Skip to content

Commit 71e1443

Browse files
committed
Add test_gpio.erl to ESP32 test suite
Adds a test to run on hardware with a jumper wire, or in the wokwi simulator to thouroghly test the esp32 gpio driver funtionality and error handling. Signed-off-by: Winford <winford@object.stream>
1 parent 2966462 commit 71e1443

File tree

9 files changed

+247
-8
lines changed

9 files changed

+247
-8
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2025 Winford <winford@object.stream>
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(test_gpio).
22+
23+
-export([start/0, test_nifs/2, test_ports/2]).
24+
25+
%% Bad argument and error reason raised if it is accepted
26+
-define(BADPINS, [
27+
{-1, accepted_neg_pin_number}, {<<0>>, accepted_binary_pin},
28+
{"GPIO_NUM_0", accepted_pin_string}, {button, accepted_pin_atom},
29+
{{a, 0}, accepted_pin_tuple}, {[0, 1, 2], accepted_pin_list},
30+
{2048, accepted_too_large}, {2.0, accepted_float}, {#{}, accepted_map}
31+
]).
32+
-define(BADMODES, [
33+
{up, accepted_badarg}, {1, accepted_int_mode}, {<<0>>, accepted_binary_mode},
34+
{<<"input">>, accepted_binary_string_mode}, {"input", accepted_mode_string},
35+
{[input, up], accepted_mode_list}, {{input, up}, accepted_mode_tuple},
36+
{2.0, accepted_float}, {#{}, accepted_map}
37+
]).
38+
-define(BADPULL, [
39+
{1, accepted_int_pull}, {<<1>>, accepted_binary_pull},
40+
{<<"up">>, accepted_binary_string_pull}, {"up", accepted_pull_string},
41+
{{up, hold}, accepted_pull_tuple}, {[up, foo, bar], accepted_pull_list},
42+
{sideways, accepted_invalid_atom}, {2.0, accepted_float}, {#{}, accepted_map}
43+
]).
44+
-define(BADLEVEL, [
45+
{medium, accepted_bad_atom}, {-1, accepted_neg_level_number},
46+
{10, accepted_badarg_number}, {<<1>>, accepted_binary_level},
47+
{<<"high">>, accepted_binary_level}, {"high", accepted_level_string},
48+
{{0, high}, accepted_level_tuple}, {[1], accepted_level_list},
49+
{1.0, accepted_float}, {#{}, accepted_map}
50+
]).
51+
52+
start() ->
53+
{Pin0, Pin1} = get_board_pins(maps:get(model, erlang:system_info(esp32_chip_info))),
54+
io:format("Starting GPIO test, this board should have a jumper wire between pins ~p and ~p.~n", [Pin0, Pin1]),
55+
ok = test_nifs(Pin0, Pin1),
56+
%% test ports with the pins reversed so we be sure to test reconfiguring an input to an output and vice versa
57+
test_ports(Pin1, Pin0).
58+
59+
test_nifs(Input, Output) ->
60+
io:format("Testing nifs raise errors for badargs... "),
61+
ok = test_nif_badargs(Input),
62+
io:format("passed.~n"),
63+
io:format("Testing set_pin_mode/2, set_pin_pull/2, digital_write/2 & digital_read/1... "),
64+
ok = gpio:set_pin_mode(Input, input),
65+
ok = gpio:set_pin_pull(Input, up),
66+
ok = gpio:set_pin_mode(Output, output),
67+
ok = gpio:set_pin_pull(Output, floating),
68+
ok = gpio:digital_write(Output, high),
69+
high = gpio:digital_read(Input),
70+
ok = gpio:digital_write(Output, low),
71+
low = gpio:digital_read(Input),
72+
ok = gpio:digital_write(Output, 1),
73+
high = gpio:digital_read(Input),
74+
ok = gpio:digital_write(Output, 0),
75+
low = gpio:digital_read(Input),
76+
io:format("passed.~n").
77+
78+
test_ports(Input, Output) ->
79+
io:format("Testing ports return {error, Reason} for badargs... "),
80+
GPIO = gpio:start(),
81+
ok = test_port_bardargs(GPIO, Input),
82+
io:format("passed.~n"),
83+
io:format("Testing set_direction/3, set_level/3 & read/2... "),
84+
ok = gpio:set_direction(GPIO, Input, input),
85+
ok = gpio:set_pin_pull(Input, up),
86+
ok = gpio:set_direction(GPIO, Output, output),
87+
ok = gpio:set_pin_pull(Output, floating),
88+
ok = gpio:set_level(GPIO, Output, low),
89+
low = gpio:read(GPIO, Input),
90+
ok = gpio:set_level(GPIO, Output, high),
91+
high = gpio:read(GPIO, Input),
92+
ok = gpio:set_level(GPIO, Output, 0),
93+
low = gpio:read(GPIO, Input),
94+
ok = gpio:set_level(GPIO, Output, 1),
95+
io:format("passed.~n"),
96+
io:format("Testing GPIO interrupt... "),
97+
Self = self(),
98+
Listener = erlang:spawn(fun() -> interrupt_listener(Input, Self) end),
99+
erlang:spawn(fun() -> interrupt_after(1000, GPIO, Output, 0) end),
100+
ok = gpio:set_int(GPIO, Input, falling, Listener),
101+
receive
102+
{ok, interrupt} -> ok;
103+
Error -> throw(Error)
104+
after
105+
5000 ->
106+
io:format("No interrupt after 5000 ms giving up"),
107+
throw(timeout_no_interrupt)
108+
end,
109+
erlang:spawn(fun() -> interrupt_after(1000, GPIO, Output, 0) end),
110+
ok = gpio:set_int(GPIO, Input, falling),
111+
receive
112+
{gpio_interrupt, Input} -> ok;
113+
Other -> throw(Other)
114+
after
115+
5000 ->
116+
io:format("No interrupt after 5000 ms giving up"),
117+
throw(timeout_no_interrupt)
118+
end,
119+
io:format("passed.~n").
120+
121+
test_nif_badargs(Pin) ->
122+
Badpin_funs1 = [ digital_read, hold_en, hold_dis ],
123+
Badpin_funs2 = [ {set_pin_mode, output}, {set_pin_pull, floating}, {digital_write, low} ],
124+
Fun_args = [ {set_pin_mode, ?BADMODES}, {set_pin_pull, ?BADPULL}, {digital_write, ?BADLEVEL} ],
125+
126+
lists:foreach(fun(TestFun) ->
127+
lists:foreach(fun({Badpin, Err}) -> ok = want_catch_throw(TestFun, Badpin, badarg, Err) end, ?BADPINS)
128+
end, Badpin_funs1),
129+
130+
lists:foreach(fun({TestFun, Arg}) ->
131+
lists:foreach(fun({Badpin, Err}) -> ok = want_catch_throw(TestFun, Badpin, Arg, badarg, Err) end, ?BADPINS)
132+
end, Badpin_funs2),
133+
134+
lists:foreach(fun({TestFun, BadArgs}) ->
135+
lists:foreach(fun({Badarg, Err}) -> ok = want_catch_throw(TestFun, Pin, Badarg, badarg, Err) end, BadArgs)
136+
end, Fun_args),
137+
ok.
138+
139+
test_port_bardargs(GPIO, Pin) ->
140+
Badpin_funs2 = [ read, remove_int ],
141+
Badpin_funs3 = [ {set_direction, input}, {set_level, low}, {set_int, low} ],
142+
Fun_args = [ {set_direction, ?BADMODES}, {set_level, ?BADLEVEL}, {set_int, ?BADLEVEL} ],
143+
144+
lists:foreach(fun(TestFun) ->
145+
lists:foreach(fun({Badpin, Err}) -> ok = want_error_tuple(TestFun, GPIO, Badpin, badarg, Err) end, ?BADPINS)
146+
end, Badpin_funs2),
147+
148+
lists:foreach(fun({TestFun, Arg}) ->
149+
lists:foreach(fun({Badpin, Err}) -> ok = want_error_tuple(TestFun, GPIO, Badpin, Arg, badarg, Err) end, ?BADPINS)
150+
end, Badpin_funs3),
151+
152+
lists:foreach(fun({TestFun, TestArgs}) ->
153+
lists:foreach(fun({Badarg, Err}) -> ok = want_error_tuple(TestFun, GPIO, Pin, Badarg, badarg, Err) end, TestArgs)
154+
end, Fun_args),
155+
ok.
156+
157+
want_catch_throw(Fun, Pin, Catch, ErrorAtom) ->
158+
try gpio:Fun(Pin) of
159+
ok ->
160+
throw({Fun, ErrorAtom});
161+
Any ->
162+
throw({Fun, Any})
163+
catch
164+
_:Catch:_ ->
165+
ok
166+
end.
167+
168+
want_catch_throw(Fun, Pin, Arg, Catch, ErrorAtom) ->
169+
try gpio:Fun(Pin, Arg) of
170+
ok ->
171+
throw({Fun, ErrorAtom});
172+
Any ->
173+
throw({Fun, Any})
174+
catch
175+
_:Catch:_ ->
176+
ok
177+
end.
178+
179+
want_error_tuple(Fun, GPIO, Pin, Reason, ErrorAtom) ->
180+
try gpio:Fun(GPIO, Pin) of
181+
ok ->
182+
throw({Fun, ErrorAtom});
183+
{error, Reason} ->
184+
ok
185+
catch
186+
Error ->
187+
throw({Fun, {caught, Error}})
188+
end.
189+
190+
want_error_tuple(Fun, GPIO, Pin, Arg, Reason, ErrorAtom) ->
191+
try gpio:Fun(GPIO, Pin, Arg) of
192+
ok ->
193+
throw({Fun, ErrorAtom});
194+
{error, Reason} ->
195+
ok
196+
catch
197+
Error ->
198+
throw({Fun, {caught, Error}})
199+
end.
200+
201+
interrupt_after(Delay, GPIO, Pin, Level) ->
202+
timer:sleep(Delay),
203+
ok = gpio:set_level(GPIO, Pin, Level),
204+
ok = gpio:set_level(GPIO, Pin, 1 - Level).
205+
206+
interrupt_listener(Pin, ReplyTo) ->
207+
receive
208+
{gpio_interrupt, Pin} ->
209+
ok = gpio:remove_int(whereis(gpio), Pin),
210+
ReplyTo ! {ok, interrupt};
211+
Any ->
212+
throw({interrupt_listener, {received, Any}})
213+
end,
214+
interrupt_listener(Pin, ReplyTo).
215+
216+
get_board_pins(Chipset) ->
217+
case (Chipset) of
218+
esp32 -> {25, 26};
219+
esp32_c3 -> {4, 5};
220+
esp32_c6 -> {6, 7};
221+
esp32_h2 -> {0, 1};
222+
esp32_p4 -> {4, 5};
223+
esp32_s2 -> {3, 4};
224+
esp32_s3 -> {4, 5};
225+
_ -> throw(unsupported_chipset)
226+
end.

src/platforms/esp32/test/main/test_main.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ TEST_CASE("test_file", "[test_run]")
289289
}
290290
#endif
291291

292+
TEST_CASE("test_gpio", "[test_run]")
293+
{
294+
term ret_value = avm_test_case("test_gpio.beam");
295+
TEST_ASSERT(term_to_int(ret_value) == OK_ATOM);
296+
}
297+
292298
TEST_CASE("test_list_to_binary", "[test_run]")
293299
{
294300
term ret_value = avm_test_case("test_list_to_binary.beam");

src/platforms/esp32/test/sim_boards/diagram.esp32.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
["sd1:DI", "esp:23", "magenta", ["h38.4", "v-96.09", "h-139.93", "v77.03"]],
3737
["pot1:VCC", "esp:3V3", "red", ["h-19.2", "v-105.6", "h-139.39"]],
3838
["pot1:GND", "esp:GND.2", "black", ["v0"]],
39-
["pot1:SIG", "esp:4", "green", ["h-38.4", "v47.2"]]
39+
["pot1:SIG", "esp:4", "green", ["h-38.4", "v47.2"]],
40+
[ "esp:25", "esp:26", "blue", [ "v0", "h-12", "v9", "h10" ] ]
4041
],
4142
"dependencies": {}
4243
}

src/platforms/esp32/test/sim_boards/diagram.esp32c3.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
[ "esp:RX", "$serialMonitor:TX", "", [] ],
1919
[ "esp:3", "pot1:SIG", "green", [ "h0" ] ],
2020
[ "esp:3V3.2", "pot1:VCC", "red", [ "h-22.98", "v38.1" ] ],
21-
[ "esp:GND.1", "pot1:GND", "black", [ "v0", "h-211.2" ] ]
21+
[ "esp:GND.1", "pot1:GND", "black", [ "v0", "h-211.2" ] ],
22+
[ "esp:4", "esp:5", "blue", [ "v0", "h12", "v-10" ] ]
2223
],
2324
"dependencies": {}
2425
}

src/platforms/esp32/test/sim_boards/diagram.esp32c6.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
[ "esp:RX", "$serialMonitor:TX", "", [] ],
1919
[ "esp:GND.1", "pot1:GND", "black", [ "h-28.8", "v48", "h-211.2" ] ],
2020
[ "esp:3V3", "pot1:VCC", "red", [ "v0", "h-38.4", "v144" ] ],
21-
[ "esp:3", "pot1:SIG", "green", [ "h0" ] ]
21+
[ "esp:3", "pot1:SIG", "green", [ "h0" ] ],
22+
[ "esp:6", "esp:7", "green", [ "h-12", "v-0" ] ]
2223
],
2324
"dependencies": {}
2425
}

src/platforms/esp32/test/sim_boards/diagram.esp32h2.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
[ "esp:RX", "$serialMonitor:TX", "", [] ],
1919
[ "esp:GND.2", "pot1:GND", "black", [ "h0" ] ],
2020
[ "esp:3V3", "pot1:VCC", "red", [ "v0", "h-19.2", "v105.6" ] ],
21-
[ "esp:4", "pot1:SIG", "green", [ "h0" ] ]
21+
[ "esp:4", "pot1:SIG", "green", [ "h0" ] ],
22+
[ "esp:0", "esp:1", "blue", [ "h-12", "v12" ] ]
2223
],
2324
"dependencies": {}
2425
}

src/platforms/esp32/test/sim_boards/diagram.esp32p4.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
],
1414
"connections": [
1515
["esp:37", "$serialMonitor:RX", "", []],
16-
["esp:38", "$serialMonitor:TX", "", []]
16+
["esp:38", "$serialMonitor:TX", "", []],
17+
[ "esp:5", "esp:4", "blue", [ "v-12", "h0", "v-10", "h10" ] ]
1718
],
1819
"serialMonitor": { "display": "terminal" },
1920
"dependencies": {}

src/platforms/esp32/test/sim_boards/diagram.esp32s2.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
"top": 77,
1111
"left": -235.8,
1212
"rotate": 180,
13-
"attrs": { "travelLength": "30", "value": "256" }
13+
"attrs": { "travelLength": "30", "value": "512" }
1414
}
1515
],
1616
"connections": [
1717
[ "esp:TX", "$serialMonitor:RX", "", [] ],
1818
[ "esp:RX", "$serialMonitor:TX", "", [] ],
1919
[ "pot1:SIG", "esp:11", "green", [ "h14.8", "v5.6" ] ],
2020
[ "esp:GND.1", "pot1:GND", "black", [ "h0" ] ],
21-
[ "pot1:VCC", "esp:3V3", "red", [ "h24.4", "v-125.29" ] ]
21+
[ "pot1:VCC", "esp:3V3", "red", [ "h24.4", "v-125.29" ] ],
22+
[ "esp:3", "esp:4", "blue", [ "h-12", "v12" ] ]
2223
],
2324
"dependencies": {}
2425
}

src/platforms/esp32/test/sim_boards/diagram.esp32s3.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
[ "esp:RX", "$serialMonitor:TX", "", [] ],
1919
[ "esp:GND.1", "pot1:GND", "black", [ "h0" ] ],
2020
[ "esp:3V3.2", "pot1:VCC", "red", [ "h-24.28", "v115.02" ] ],
21-
[ "esp:11", "pot1:SIG", "green", [ "h-14.68", "v-48.18" ] ]
21+
[ "esp:11", "pot1:SIG", "green", [ "h-14.68", "v-48.18" ] ],
22+
[ "esp:4", "esp:5", "green", [ "h-12", "v10" ] ]
2223
],
2324
"dependencies": {}
2425
}

0 commit comments

Comments
 (0)