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