Skip to content

Commit 9e2a29d

Browse files
committed
Add SPI support to RP2 platform
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 854a71e commit 9e2a29d

File tree

8 files changed

+1011
-14
lines changed

8 files changed

+1011
-14
lines changed

CMakeModules/BuildErlang.cmake

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ endmacro()
267267

268268
macro(pack_runnable avm_name main)
269269

270+
set(multiValueArgs DIALYZE_AGAINST)
271+
cmake_parse_arguments(PACK_RUNNABLE "" "" "${multiValueArgs}" ${ARGN})
272+
270273
add_custom_command(
271274
OUTPUT ${main}.beam
272275
COMMAND erlc +debug_info -I ${CMAKE_SOURCE_DIR}/libs/include ${CMAKE_CURRENT_SOURCE_DIR}/${main}.erl
@@ -281,9 +284,9 @@ macro(pack_runnable avm_name main)
281284
)
282285

283286
# Select the right PLT based on platform-specific dependencies
284-
set(pack_runnable_${avm_name}_plt_name "atomvmlib")
287+
set(pack_runnable_${avm_name}_plt_names "")
285288

286-
foreach(archive_name ${ARGN})
289+
foreach(archive_name ${PACK_RUNNABLE_UNPARSED_ARGUMENTS})
287290
if(NOT ${archive_name} STREQUAL "exavmlib")
288291
set(pack_runnable_${avm_name}_archives ${pack_runnable_${avm_name}_archives} ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/${archive_name}.avm)
289292
if(NOT ${archive_name} MATCHES "^eavmlib|estdlib|alisp|avm_network|avm_esp32|avm_rp2|avm_stm32|avm_emscripten$")
@@ -295,24 +298,47 @@ macro(pack_runnable avm_name main)
295298
set(pack_runnable_${avm_name}_archive_targets ${pack_runnable_${avm_name}_archive_targets} ${archive_name})
296299
# Pick the platform-specific PLT if a platform library is in the dependencies
297300
if(${archive_name} STREQUAL "avm_esp32")
298-
set(pack_runnable_${avm_name}_plt_name "atomvmlib-esp32")
301+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-esp32")
299302
elseif(${archive_name} STREQUAL "avm_rp2")
300-
set(pack_runnable_${avm_name}_plt_name "atomvmlib-rp2")
303+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-rp2")
301304
elseif(${archive_name} STREQUAL "avm_stm32")
302-
set(pack_runnable_${avm_name}_plt_name "atomvmlib-stm32")
305+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-stm32")
303306
elseif(${archive_name} STREQUAL "avm_emscripten")
304-
set(pack_runnable_${avm_name}_plt_name "atomvmlib-emscripten")
307+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-emscripten")
305308
endif()
306309
endforeach()
307310

311+
# DIALYZE_AGAINST overrides auto-detected PLTs
312+
if(PACK_RUNNABLE_DIALYZE_AGAINST)
313+
set(pack_runnable_${avm_name}_plt_names "")
314+
foreach(plt_lib IN LISTS PACK_RUNNABLE_DIALYZE_AGAINST)
315+
if(${plt_lib} STREQUAL "avm_esp32")
316+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-esp32")
317+
elseif(${plt_lib} STREQUAL "avm_rp2")
318+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-rp2")
319+
elseif(${plt_lib} STREQUAL "avm_stm32")
320+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-stm32")
321+
elseif(${plt_lib} STREQUAL "avm_emscripten")
322+
list(APPEND pack_runnable_${avm_name}_plt_names "atomvmlib-emscripten")
323+
endif()
324+
endforeach()
325+
endif()
326+
327+
# Default to base PLT if no platform was detected
328+
if(NOT pack_runnable_${avm_name}_plt_names)
329+
set(pack_runnable_${avm_name}_plt_names "atomvmlib")
330+
endif()
331+
308332
if (Dialyzer_FOUND)
309-
add_custom_target(
310-
${avm_name}_dialyzer
311-
DEPENDS ${avm_name}_main
312-
COMMAND dialyzer --plt ${CMAKE_BINARY_DIR}/libs/${pack_runnable_${avm_name}_plt_name}.plt -c ${main}.beam ${${avm_name}_dialyzer_beams_opt}
313-
)
314-
add_dependencies(${avm_name}_dialyzer ${pack_runnable_${avm_name}_plt_name}_plt ${pack_runnable_${avm_name}_archive_targets})
315-
add_dependencies(dialyzer ${avm_name}_dialyzer)
333+
foreach(plt_name IN LISTS pack_runnable_${avm_name}_plt_names)
334+
add_custom_target(
335+
${avm_name}_${plt_name}_dialyzer
336+
DEPENDS ${avm_name}_main
337+
COMMAND dialyzer --plt ${CMAKE_BINARY_DIR}/libs/${plt_name}.plt -c ${main}.beam ${${avm_name}_dialyzer_beams_opt}
338+
)
339+
add_dependencies(${avm_name}_${plt_name}_dialyzer ${plt_name}_plt ${pack_runnable_${avm_name}_archive_targets})
340+
add_dependencies(dialyzer ${avm_name}_${plt_name}_dialyzer)
341+
endforeach()
316342
endif()
317343

318344
if(AVM_RELEASE)

examples/erlang/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ pack_runnable(network_console network_console estdlib eavmlib alisp)
4343
pack_runnable(logging_example logging_example estdlib eavmlib)
4444
pack_runnable(http_client http_client estdlib eavmlib avm_network)
4545
pack_runnable(disterl disterl estdlib)
46+
pack_runnable(spi_flash spi_flash eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2)

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

0 commit comments

Comments
 (0)