Skip to content

Commit 33d6804

Browse files
committed
Add SPI support for RP2
Also add a sample code, `spi_flash.erl` confirmed to work on both Pico-W and ESP32C2, using the same high level SPI API. Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 53ff0bb commit 33d6804

File tree

5 files changed

+151
-1
lines changed

5 files changed

+151
-1
lines changed

examples/erlang/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ pack_runnable(network_console network_console estdlib eavmlib alisp)
4242
pack_runnable(logging_example logging_example estdlib eavmlib)
4343
pack_runnable(http_client http_client estdlib eavmlib avm_network)
4444
pack_runnable(disterl disterl estdlib)
45+
pack_runnable(spi_flash spi_flash eavmlib estdlib)

examples/erlang/rp2/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ pack_uf2(pico_blink pico_blink)
2727
pack_uf2(pico_i2c_scanner pico_i2c_scanner)
2828
pack_uf2(pico_lis3dh pico_lis3dh)
2929
pack_uf2(pico_rtc pico_rtc)
30+
31+
set(SPI_FLASH_AVM ${CMAKE_BINARY_DIR}/examples/erlang/spi_flash.avm)
32+
add_custom_command(
33+
OUTPUT spi_flash.uf2
34+
DEPENDS ${SPI_FLASH_AVM} UF2Tool
35+
COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o spi_flash.uf2 -f universal -s 0x10180000 ${SPI_FLASH_AVM}
36+
COMMENT "Creating UF2 file spi_flash.uf2"
37+
VERBATIM
38+
)
39+
add_custom_target(spi_flash_uf2 ALL DEPENDS spi_flash.uf2)
40+
add_dependencies(spi_flash_uf2 spi_flash)
3041
pack_uf2(picow_blink picow_blink)
3142
pack_uf2(picow_wifi_sta picow_wifi_sta)
3243
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.

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})

src/platforms/rp2/src/lib/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ set(HEADER_FILES
3232
set(SOURCE_FILES
3333
gpiodriver.c
3434
i2cdriver.c
35+
spidriver.c
3536
networkdriver.c
3637
otp_crypto_platform.c
3738
platform_defaultatoms.c
@@ -59,6 +60,7 @@ target_link_libraries(
5960
PUBLIC
6061
hardware_gpio
6162
hardware_i2c
63+
hardware_spi
6264
hardware_sync
6365
pico_float
6466
pico_mbedtls
@@ -123,4 +125,4 @@ if (NOT AVM_DISABLE_JIT)
123125
target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,jit_stream_flash_get_nif")
124126
endif()
125127

126-
target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif -Wl,-u -Wl,i2c_nif -Wl,-u -Wl,otp_crypto_nif")
128+
target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif -Wl,-u -Wl,i2c_nif -Wl,-u -Wl,spi_nif -Wl,-u -Wl,otp_crypto_nif")

0 commit comments

Comments
 (0)