|
| 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. |
0 commit comments