Skip to content

Commit c6efd00

Browse files
committed
Add SPI support to RP2 platform
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 006aa37 commit c6efd00

8 files changed

Lines changed: 1600 additions & 1 deletion

File tree

examples/erlang/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ pack_runnable(http_client http_client estdlib eavmlib avm_network)
4545
pack_runnable(disterl disterl estdlib)
4646
pack_runnable(i2c_scanner i2c_scanner eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32)
4747
pack_runnable(i2c_lis3dh i2c_lis3dh eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32)
48+
pack_runnable(spi_flash spi_flash eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2)
49+
pack_runnable(spi_lis3dh spi_lis3dh eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2)

examples/erlang/rp2/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ add_custom_command(
4848
add_custom_target(i2c_lis3dh_uf2 ALL DEPENDS i2c_lis3dh.uf2)
4949
add_dependencies(i2c_lis3dh_uf2 i2c_lis3dh)
5050

51+
set(SPI_FLASH_AVM ${CMAKE_BINARY_DIR}/examples/erlang/spi_flash.avm)
52+
add_custom_command(
53+
OUTPUT spi_flash.uf2
54+
DEPENDS ${SPI_FLASH_AVM} UF2Tool
55+
COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o spi_flash.uf2 -f universal -s 0x10180000 ${SPI_FLASH_AVM}
56+
COMMENT "Creating UF2 file spi_flash.uf2"
57+
VERBATIM
58+
)
59+
add_custom_target(spi_flash_uf2 ALL DEPENDS spi_flash.uf2)
60+
add_dependencies(spi_flash_uf2 spi_flash)
61+
62+
set(SPI_LIS3DH_AVM ${CMAKE_BINARY_DIR}/examples/erlang/spi_lis3dh.avm)
63+
add_custom_command(
64+
OUTPUT spi_lis3dh.uf2
65+
DEPENDS ${SPI_LIS3DH_AVM} UF2Tool
66+
COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o spi_lis3dh.uf2 -f universal -s 0x10180000 ${SPI_LIS3DH_AVM}
67+
COMMENT "Creating UF2 file spi_lis3dh.uf2"
68+
VERBATIM
69+
)
70+
add_custom_target(spi_lis3dh_uf2 ALL DEPENDS spi_lis3dh.uf2)
71+
add_dependencies(spi_lis3dh_uf2 spi_lis3dh)
72+
5173
pack_uf2(picow_blink picow_blink)
5274
pack_uf2(picow_wifi_sta picow_wifi_sta)
5375
pack_uf2(picow_wifi_ap picow_wifi_ap)

examples/erlang/spi_flash.erl

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 SPI flash JEDEC ID reader example.
23+
%%
24+
%% Reads the JEDEC ID and Status Register 1 from a standard SPI flash chip
25+
%% (W25Qxx or similar) and prints the results every 5 seconds.
26+
%%
27+
%% Default pins are auto-detected from the platform and chip model:
28+
%%
29+
%% Pico (SPI0): SCK=GP2, MOSI=GP3, MISO=GP4, CS=GP5
30+
%% ESP32/S2/S3 (SPI2): SCK=18, MOSI=23, MISO=19, CS=5
31+
%% ESP32-C2/C3/C5 (SPI2): SCK=6, MOSI=7, MISO=2, CS=10
32+
%% ESP32-C6/C61 (SPI2): SCK=6, MOSI=7, MISO=2, CS=16
33+
%%
34+
%% Note: some breakout boards label pins using QSPI convention where
35+
%% D0 is MISO and D1 is MOSI (opposite of DI/DO). If you read all
36+
%% zeros, try swapping D0 and D1.
37+
%%
38+
%% The flash command byte is sent as the "address" parameter of read_at/4,
39+
%% using address_len_bits => 8 and command_len_bits => 0.
40+
%% @end
41+
%%-----------------------------------------------------------------------------
42+
-module(spi_flash).
43+
-export([start/0]).
44+
45+
%% SPI flash commands
46+
-define(CMD_JEDEC_ID, 16#9F).
47+
-define(CMD_READ_STATUS1, 16#05).
48+
49+
start() ->
50+
{SCK, MOSI, MISO, CS} = default_pins(),
51+
SPI = spi:open([
52+
{bus_config, [
53+
{sclk, SCK},
54+
{mosi, MOSI},
55+
{miso, MISO}
56+
]},
57+
{device_config, [
58+
{flash, [
59+
{cs, CS},
60+
{clock_speed_hz, 1000000},
61+
{mode, 0},
62+
{address_len_bits, 8},
63+
{command_len_bits, 0}
64+
]}
65+
]}
66+
]),
67+
loop(SPI).
68+
69+
loop(SPI) ->
70+
read_jedec_id(SPI),
71+
read_status(SPI),
72+
timer:sleep(5000),
73+
loop(SPI).
74+
75+
read_jedec_id(SPI) ->
76+
case spi:read_at(SPI, flash, ?CMD_JEDEC_ID, 24) of
77+
{ok, Value} ->
78+
Manufacturer = (Value bsr 16) band 16#FF,
79+
MemType = (Value bsr 8) band 16#FF,
80+
Capacity = Value band 16#FF,
81+
io:format(
82+
"JEDEC ID: manufacturer=0x~2.16.0B mem_type=0x~2.16.0B capacity=0x~2.16.0B~n", [
83+
Manufacturer, MemType, Capacity
84+
]
85+
),
86+
case manufacturer_name(Manufacturer) of
87+
unknown -> ok;
88+
Name -> io:format(" Manufacturer: ~s~n", [Name])
89+
end;
90+
{error, Reason} ->
91+
io:format("JEDEC ID read error: ~p~n", [Reason])
92+
end.
93+
94+
read_status(SPI) ->
95+
case spi:read_at(SPI, flash, ?CMD_READ_STATUS1, 8) of
96+
{ok, Status} ->
97+
Busy = Status band 1,
98+
Wel = (Status bsr 1) band 1,
99+
io:format("Status Register 1: 0x~2.16.0B (BUSY=~B WEL=~B)~n", [
100+
Status, Busy, Wel
101+
]);
102+
{error, Reason} ->
103+
io:format("Status read error: ~p~n", [Reason])
104+
end.
105+
106+
default_pins() ->
107+
default_pins(atomvm:platform()).
108+
109+
%% {SCK, MOSI, MISO, CS}
110+
default_pins(pico) -> {2, 3, 4, 5};
111+
default_pins(esp32) -> esp32_default_pins().
112+
113+
esp32_default_pins() ->
114+
#{model := Model} = erlang:system_info(esp32_chip_info),
115+
esp32_default_pins(Model).
116+
117+
%% {SCK, MOSI, MISO, CS}
118+
esp32_default_pins(esp32) -> {18, 23, 19, 5};
119+
esp32_default_pins(esp32_s2) -> {18, 23, 19, 5};
120+
esp32_default_pins(esp32_s3) -> {18, 23, 19, 5};
121+
esp32_default_pins(esp32_c2) -> {6, 7, 2, 10};
122+
esp32_default_pins(esp32_c3) -> {6, 7, 2, 10};
123+
esp32_default_pins(esp32_c5) -> {6, 7, 2, 10};
124+
esp32_default_pins(esp32_c6) -> {6, 7, 2, 16};
125+
esp32_default_pins(esp32_c61) -> {6, 7, 2, 16};
126+
esp32_default_pins(_) -> {18, 23, 19, 5}.
127+
128+
manufacturer_name(16#EF) -> "Winbond";
129+
manufacturer_name(16#C8) -> "GigaDevice";
130+
manufacturer_name(16#20) -> "Micron";
131+
manufacturer_name(16#01) -> "Spansion/Cypress";
132+
manufacturer_name(16#1F) -> "Adesto/Atmel";
133+
manufacturer_name(16#BF) -> "Microchip/SST";
134+
manufacturer_name(16#9D) -> "ISSI";
135+
manufacturer_name(_) -> unknown.

examples/erlang/spi_lis3dh.erl

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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 SPI example.
23+
%%
24+
%% Reads X, Y, Z acceleration from a LIS3DH connected via SPI and prints
25+
%% the values every second.
26+
%%
27+
%% Default pins are auto-detected from the platform and chip model:
28+
%%
29+
%% Pico (SPI0): SCK=GP2, MOSI=GP3, MISO=GP4, CS=GP5
30+
%% ESP32/S2/S3 (SPI2): SCK=18, MOSI=23, MISO=19, CS=5
31+
%% ESP32-C2/C3/C5 (SPI2): SCK=6, MOSI=7, MISO=2, CS=10
32+
%% ESP32-C6/C61 (SPI2): SCK=6, MOSI=7, MISO=2, CS=16
33+
%%
34+
%% LIS3DH SPI protocol: bit 7 = R/W (1=read, 0=write),
35+
%% bit 6 = MS (1=auto-increment).
36+
%% @end
37+
%%-----------------------------------------------------------------------------
38+
-module(spi_lis3dh).
39+
-export([start/0]).
40+
41+
%% LIS3DH registers
42+
-define(WHO_AM_I, 16#0F).
43+
-define(CTRL_REG1, 16#20).
44+
-define(CTRL_REG4, 16#23).
45+
-define(OUT_X_L, 16#28).
46+
47+
%% Expected WHO_AM_I response
48+
-define(LIS3DH_ID, 16#33).
49+
50+
%% SPI address bits: bit 7 = read, bit 6 = auto-increment
51+
-define(READ_BIT, 16#80).
52+
-define(MS_BIT, 16#40).
53+
54+
start() ->
55+
{SCK, MOSI, MISO, CS} = default_pins(),
56+
SPI = spi:open([
57+
{bus_config, [
58+
{sclk, SCK},
59+
{mosi, MOSI},
60+
{miso, MISO}
61+
]},
62+
{device_config, [
63+
{lis3dh, [
64+
{cs, CS},
65+
{clock_speed_hz, 1000000},
66+
{mode, 0},
67+
{address_len_bits, 8},
68+
{command_len_bits, 0}
69+
]}
70+
]}
71+
]),
72+
case check_who_am_i(SPI) of
73+
ok ->
74+
configure(SPI),
75+
loop(SPI);
76+
{error, Reason} ->
77+
io:format("LIS3DH not found: ~p~n", [Reason])
78+
end.
79+
80+
check_who_am_i(SPI) ->
81+
case spi:read_at(SPI, lis3dh, ?READ_BIT bor ?WHO_AM_I, 8) of
82+
{ok, ?LIS3DH_ID} ->
83+
io:format("LIS3DH detected (SPI)~n"),
84+
ok;
85+
{ok, Other} ->
86+
io:format("Unexpected WHO_AM_I: ~.16B~n", [Other]),
87+
{error, unexpected_id};
88+
{error, _} = Error ->
89+
Error
90+
end.
91+
92+
configure(SPI) ->
93+
%% CTRL_REG1: 50 Hz ODR, normal mode, X/Y/Z enabled
94+
%% Bits: ODR=0100 LPen=0 Zen=1 Yen=1 Xen=1 -> 0x47
95+
ok = spi:write(SPI, lis3dh, #{address => ?CTRL_REG1, write_data => <<16#47:8>>}),
96+
%% CTRL_REG4: +/- 2g full scale, high resolution
97+
%% Bits: BDU=1 BLE=0 FS=00 HR=1 ST=00 SIM=0 -> 0x88
98+
ok = spi:write(SPI, lis3dh, #{address => ?CTRL_REG4, write_data => <<16#88:8>>}).
99+
100+
loop(SPI) ->
101+
case read_acceleration(SPI) of
102+
{ok, {X, Y, Z}} ->
103+
io:format("X=~B Y=~B Z=~B~n", [X, Y, Z]);
104+
{error, Reason} ->
105+
io:format("Read error: ~p~n", [Reason])
106+
end,
107+
timer:sleep(1000),
108+
loop(SPI).
109+
110+
read_acceleration(SPI) ->
111+
%% Read 6 bytes starting at OUT_X_L with auto-increment (MS bit set)
112+
case spi:read_at(SPI, lis3dh, ?READ_BIT bor ?MS_BIT bor ?OUT_X_L, 48) of
113+
{ok, Value} ->
114+
XL = (Value bsr 40) band 16#FF,
115+
XH = (Value bsr 32) band 16#FF,
116+
YL = (Value bsr 24) band 16#FF,
117+
YH = (Value bsr 16) band 16#FF,
118+
ZL = (Value bsr 8) band 16#FF,
119+
ZH = Value band 16#FF,
120+
%% 12-bit left-justified in high-resolution mode: shift right by 4
121+
X = sign_extend_12(((XH bsl 8) bor XL) bsr 4),
122+
Y = sign_extend_12(((YH bsl 8) bor YL) bsr 4),
123+
Z = sign_extend_12(((ZH bsl 8) bor ZL) bsr 4),
124+
{ok, {X, Y, Z}};
125+
{error, _} = Error ->
126+
Error
127+
end.
128+
129+
sign_extend_12(V) when V >= 16#800 -> V - 16#1000;
130+
sign_extend_12(V) -> V.
131+
132+
default_pins() ->
133+
default_pins(atomvm:platform()).
134+
135+
%% {SCK, MOSI, MISO, CS}
136+
default_pins(pico) -> {2, 3, 4, 5};
137+
default_pins(esp32) -> esp32_default_pins().
138+
139+
esp32_default_pins() ->
140+
#{model := Model} = erlang:system_info(esp32_chip_info),
141+
esp32_default_pins(Model).
142+
143+
%% {SCK, MOSI, MISO, CS}
144+
esp32_default_pins(esp32) -> {18, 23, 19, 5};
145+
esp32_default_pins(esp32_s2) -> {18, 23, 19, 5};
146+
esp32_default_pins(esp32_s3) -> {18, 23, 19, 5};
147+
esp32_default_pins(esp32_c2) -> {6, 7, 2, 10};
148+
esp32_default_pins(esp32_c3) -> {6, 7, 2, 10};
149+
esp32_default_pins(esp32_c5) -> {6, 7, 2, 10};
150+
esp32_default_pins(esp32_c6) -> {6, 7, 2, 16};
151+
esp32_default_pins(esp32_c61) -> {6, 7, 2, 16};
152+
esp32_default_pins(_) -> {18, 23, 19, 5}.

libs/avm_rp2/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set(ERLANG_MODULES
2626
gpio
2727
i2c
2828
pico
29+
spi
2930
)
3031

3132
pack_archive(avm_rp2 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES})

0 commit comments

Comments
 (0)