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