Skip to content

Commit 9e63c9b

Browse files
committed
Add I2C support to RP2 platform
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 700a9a1 commit 9e63c9b

File tree

9 files changed

+987
-1
lines changed

9 files changed

+987
-1
lines changed

examples/erlang/rp2/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ include(BuildErlang)
2424

2525
pack_uf2(hello_pico hello_pico)
2626
pack_uf2(pico_blink pico_blink)
27+
pack_uf2(pico_i2c_scanner pico_i2c_scanner)
28+
pack_uf2(pico_lis3dh pico_lis3dh)
2729
pack_uf2(pico_rtc pico_rtc)
2830
pack_uf2(picow_blink picow_blink)
2931
pack_uf2(picow_wifi_sta picow_wifi_sta)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2026 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+
%%-----------------------------------------------------------------------------
22+
%% @doc I2C bus scanner example for Pico.
23+
%%
24+
%% Scans all valid 7-bit I2C addresses (0x08-0x77) and prints which devices
25+
%% respond with an ACK.
26+
%%
27+
%% Default wiring (Pico pin numbers):
28+
%% SDA -> GP4 (pin 6)
29+
%% SCL -> GP5 (pin 7)
30+
%% GND -> pin 8
31+
%% 3V3 -> pin 36
32+
%%
33+
%% These are the default I2C0 pins on the Pico.
34+
%% @end
35+
%%-----------------------------------------------------------------------------
36+
-module(pico_i2c_scanner).
37+
-export([start/0]).
38+
39+
%% I2C pins (I2C0 default on Pico)
40+
-define(SDA_PIN, 4).
41+
-define(SCL_PIN, 5).
42+
43+
start() ->
44+
I2C = i2c:open([
45+
{scl, ?SCL_PIN},
46+
{sda, ?SDA_PIN},
47+
{clock_speed_hz, 100000},
48+
{peripheral, 0}
49+
]),
50+
console:puts("I2C bus scan (0x08-0x77):\n"),
51+
Found = scan(I2C, 16#08, []),
52+
case Found of
53+
[] ->
54+
console:puts("No devices found.\n");
55+
_ ->
56+
console:puts("Done. Found "),
57+
console:puts(erlang:integer_to_list(length(Found))),
58+
console:puts(" device(s).\n")
59+
end.
60+
61+
scan(_I2C, Addr, Acc) when Addr > 16#77 ->
62+
lists:reverse(Acc);
63+
scan(I2C, Addr, Acc) ->
64+
NewAcc =
65+
case i2c:read_bytes(I2C, Addr, 1) of
66+
{ok, _} ->
67+
console:puts(" 0x"),
68+
console:puts(erlang:integer_to_list(Addr, 16)),
69+
console:puts(" ACK\n"),
70+
[Addr | Acc];
71+
{error, _} ->
72+
Acc
73+
end,
74+
scan(I2C, Addr + 1, NewAcc).
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2026 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+
%%-----------------------------------------------------------------------------
22+
%% @doc LIS3DH accelerometer example for Pico.
23+
%%
24+
%% Reads X, Y, Z acceleration from a LIS3DH connected via I2C and prints
25+
%% the values every second.
26+
%%
27+
%% Default wiring (Pico pin numbers):
28+
%% SDA -> GP4 (pin 6)
29+
%% SCL -> GP5 (pin 7)
30+
%%
31+
%% These are the default I2C0 pins on the Pico.
32+
%% @end
33+
%%-----------------------------------------------------------------------------
34+
-module(pico_lis3dh).
35+
-export([start/0]).
36+
37+
%% I2C pins (I2C0 default on Pico)
38+
-define(SDA_PIN, 4).
39+
-define(SCL_PIN, 5).
40+
41+
%% LIS3DH I2C address (0x18 when SDO/SA0 is low, 0x19 when high)
42+
-define(LIS3DH_ADDR, 16#19).
43+
44+
%% LIS3DH registers
45+
-define(WHO_AM_I, 16#0F).
46+
-define(CTRL_REG1, 16#20).
47+
-define(CTRL_REG4, 16#23).
48+
-define(OUT_X_L, 16#28).
49+
50+
%% Expected WHO_AM_I response
51+
-define(LIS3DH_ID, 16#33).
52+
53+
start() ->
54+
I2C = i2c:open([
55+
{scl, ?SCL_PIN},
56+
{sda, ?SDA_PIN},
57+
{clock_speed_hz, 400000},
58+
{peripheral, 0}
59+
]),
60+
case check_who_am_i(I2C) of
61+
ok ->
62+
configure(I2C),
63+
loop(I2C);
64+
{error, Reason} ->
65+
console:puts("LIS3DH not found: "),
66+
console:puts(erlang:atom_to_list(Reason)),
67+
console:puts("\n")
68+
end.
69+
70+
check_who_am_i(I2C) ->
71+
case i2c:read_bytes(I2C, ?LIS3DH_ADDR, ?WHO_AM_I, 1) of
72+
{ok, <<?LIS3DH_ID:8>>} ->
73+
console:puts("LIS3DH detected\n"),
74+
ok;
75+
{ok, <<Other:8>>} ->
76+
console:puts("Unexpected WHO_AM_I: "),
77+
console:puts(erlang:integer_to_list(Other, 16)),
78+
console:puts("\n"),
79+
{error, unexpected_id};
80+
{error, _} = Error ->
81+
Error
82+
end.
83+
84+
configure(I2C) ->
85+
%% CTRL_REG1: 50 Hz ODR, normal mode, X/Y/Z enabled
86+
%% Bits: ODR=0100 LPen=0 Zen=1 Yen=1 Xen=1 -> 0x47
87+
ok = i2c:write_bytes(I2C, ?LIS3DH_ADDR, ?CTRL_REG1, 16#47),
88+
%% CTRL_REG4: +/- 2g full scale, high resolution
89+
%% Bits: BDU=1 BLE=0 FS=00 HR=1 ST=00 SIM=0 -> 0x88
90+
ok = i2c:write_bytes(I2C, ?LIS3DH_ADDR, ?CTRL_REG4, 16#88).
91+
92+
loop(I2C) ->
93+
case read_acceleration(I2C) of
94+
{ok, {X, Y, Z}} ->
95+
console:puts("X="),
96+
console:puts(erlang:integer_to_list(X)),
97+
console:puts(" Y="),
98+
console:puts(erlang:integer_to_list(Y)),
99+
console:puts(" Z="),
100+
console:puts(erlang:integer_to_list(Z)),
101+
console:puts("\n");
102+
{error, Reason} ->
103+
console:puts("Read error: "),
104+
console:puts(erlang:atom_to_list(Reason)),
105+
console:puts("\n")
106+
end,
107+
timer:sleep(1000),
108+
loop(I2C).
109+
110+
read_acceleration(I2C) ->
111+
%% Read 6 bytes starting at OUT_X_L with auto-increment (bit 7 set)
112+
case i2c:read_bytes(I2C, ?LIS3DH_ADDR, ?OUT_X_L bor 16#80, 6) of
113+
{ok, <<XL:8, XH:8/signed, YL:8, YH:8/signed, ZL:8, ZH:8/signed>>} ->
114+
%% 12-bit left-justified in high-resolution mode: shift right by 4
115+
X = ((XH bsl 8) bor XL) bsr 4,
116+
Y = ((YH bsl 8) bor YL) bsr 4,
117+
Z = ((ZH bsl 8) bor ZL) bsr 4,
118+
{ok, {X, Y, Z}};
119+
{error, _} = Error ->
120+
Error
121+
end.

libs/avm_rp2/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ include(BuildErlang)
2424

2525
set(ERLANG_MODULES
2626
gpio
27+
i2c
2728
pico
2829
)
2930

libs/avm_rp2/src/gpio.erl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
-export([
3535
init/1,
3636
deinit/1,
37+
set_function/2,
3738
set_pin_mode/2,
3839
set_pin_pull/2,
3940
digital_write/2,
@@ -44,6 +45,8 @@
4445
%% The pin definition for RP2040 is a non-negative integer. A tuple is used for the extra "WL" pins on the Pico-W.
4546
-type pin_tuple() :: {wl, 0..2}.
4647
%% The extra "WL" pins on Pico-W use bank `wl'.
48+
-type gpio_function() :: spi | uart | i2c | pwm | sio | pio0 | pio1.
49+
%% GPIO function select values matching Pico SDK gpio_function_t.
4750
-type direction() :: input | output | output_od.
4851
%% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain.
4952
-type pull() :: up | down | up_down | floating.
@@ -74,6 +77,20 @@ init(_Pin) ->
7477
deinit(_Pin) ->
7578
ok.
7679

80+
%%-----------------------------------------------------------------------------
81+
%% @param Pin GPIO pin number
82+
%% @param Function the function to assign to the pin
83+
%% @returns ok
84+
%% @doc Select the function for a GPIO pin.
85+
%%
86+
%% Maps to `gpio_set_function()' in the Pico SDK.
87+
%% Common functions: `sio' (default GPIO), `i2c', `spi', `uart', `pwm'.
88+
%% @end
89+
%%-----------------------------------------------------------------------------
90+
-spec set_function(Pin :: non_neg_integer(), Function :: gpio_function()) -> ok.
91+
set_function(_Pin, _Function) ->
92+
erlang:nif_error(undefined).
93+
7794
%%-----------------------------------------------------------------------------
7895
%% @param Pin number to set operational mode
7996
%% @param Direction is `input', `output', or `output_od'

0 commit comments

Comments
 (0)