Skip to content

Commit 1e6b792

Browse files
committed
Add I2C support to STM32 platform
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent e738979 commit 1e6b792

File tree

13 files changed

+1291
-1
lines changed

13 files changed

+1291
-1
lines changed

.github/workflows/stm32-build.yaml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ jobs:
5757
max_size: 393216
5858
renode_platform: stm32f4.repl
5959
avm_address: "0x08060000"
60+
# Renode's STM32F4_I2C model has a bug: it never calls
61+
# FinishTransmission() on the I2C slave when a STOP condition
62+
# occurs, causing the BME280 sensor to get stuck in Reading
63+
# state and ignore subsequent writes.
64+
skip_i2c_test: true
6065
- device: stm32h743vit6
6166
max_size: 524288
6267
renode_platform: stm32h743.repl
@@ -79,6 +84,10 @@ jobs:
7984
max_size: 393216
8085
renode_platform: stm32l562.repl
8186
avm_address: "0x08060000"
87+
# Renode's built-in stm32l552.repl uses STM32F4_I2C (legacy I2C
88+
# register layout) but the L5 HAL uses the newer I2C registers
89+
# (TIMINGR, ISR, etc.), causing a complete register mismatch.
90+
skip_i2c_test: true
8291
- device: stm32f207zgt6
8392
max_size: 524288
8493
- device: stm32u375rgt6
@@ -149,7 +158,7 @@ jobs:
149158
mkdir build-host
150159
cd build-host
151160
cmake .. -G Ninja
152-
cmake --build . -t stm32_boot_test stm32_gpio_test
161+
cmake --build . -t stm32_boot_test stm32_gpio_test stm32_i2c_test
153162
154163
- name: Install Renode
155164
if: matrix.renode_platform
@@ -190,3 +199,18 @@ jobs:
190199
--variable AVM:@$PWD/build-host/src/platforms/stm32/tests/test_erl_sources/stm32_gpio_test.avm \
191200
--variable AVM_ADDRESS:${{ matrix.avm_address }} \
192201
--variable PLATFORM:$PLATFORM
202+
203+
- name: Run Renode I2C test
204+
if: matrix.renode_platform && !matrix.skip_i2c_test
205+
run: |
206+
LOCAL_REPL="src/platforms/stm32/tests/renode/${{ matrix.renode_platform }}"
207+
if [ -f "$LOCAL_REPL" ]; then
208+
PLATFORM="@$PWD/$LOCAL_REPL"
209+
else
210+
PLATFORM="@platforms/cpus/${{ matrix.renode_platform }}"
211+
fi
212+
renode-test src/platforms/stm32/tests/renode/stm32_i2c_test.robot \
213+
--variable ELF:@$PWD/src/platforms/stm32/build/AtomVM-${{ matrix.device }}.elf \
214+
--variable AVM:@$PWD/build-host/src/platforms/stm32/tests/test_erl_sources/stm32_i2c_test.avm \
215+
--variable AVM_ADDRESS:${{ matrix.avm_address }} \
216+
--variable PLATFORM:$PLATFORM

examples/erlang/stm32/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ pack_runnable(blink_weact_studio_h562 blink_weact_studio_h562 eavmlib avm_stm32)
2727
pack_runnable(blink_weact_studio_h743 blink_weact_studio_h743 eavmlib avm_stm32)
2828
pack_runnable(blink_weact_studio_u585 blink_weact_studio_u585 eavmlib avm_stm32)
2929
pack_runnable(blink_weact_studio_wb55 blink_weact_studio_wb55 eavmlib avm_stm32)
30+
pack_runnable(stm32_lis3dh stm32_lis3dh eavmlib avm_stm32)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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 example for STM32.
23+
%%
24+
%% Reads X, Y, Z acceleration from a LIS3DH connected via I2C and prints
25+
%% the values every second.
26+
%%
27+
%% Default wiring (I2C1 on most STM32 boards):
28+
%% SCL -> PB6
29+
%% SDA -> PB7
30+
%%
31+
%% The alternate function number defaults to 4, which is correct for I2C1
32+
%% on most STM32 families. Adjust `af' if your chip uses a different mapping.
33+
%% @end
34+
%%-----------------------------------------------------------------------------
35+
-module(stm32_lis3dh).
36+
-export([start/0]).
37+
38+
%% I2C1 pins (PB6/PB7 are common I2C1 pins across STM32 families)
39+
-define(SCL_PIN, {b, 6}).
40+
-define(SDA_PIN, {b, 7}).
41+
42+
%% LIS3DH I2C address (0x18 when SDO/SA0 is low, 0x19 when high)
43+
-define(LIS3DH_ADDR, 16#19).
44+
45+
%% LIS3DH registers
46+
-define(WHO_AM_I, 16#0F).
47+
-define(CTRL_REG1, 16#20).
48+
-define(CTRL_REG4, 16#23).
49+
-define(OUT_X_L, 16#28).
50+
51+
%% Expected WHO_AM_I response
52+
-define(LIS3DH_ID, 16#33).
53+
54+
start() ->
55+
I2C = i2c:open([
56+
{scl, ?SCL_PIN},
57+
{sda, ?SDA_PIN},
58+
{clock_speed_hz, 400000},
59+
{peripheral, 1}
60+
]),
61+
case check_who_am_i(I2C) of
62+
ok ->
63+
configure(I2C),
64+
loop(I2C);
65+
{error, Reason} ->
66+
console:puts("LIS3DH not found: "),
67+
console:puts(erlang:atom_to_list(Reason)),
68+
console:puts("\n")
69+
end.
70+
71+
check_who_am_i(I2C) ->
72+
case i2c:read_bytes(I2C, ?LIS3DH_ADDR, ?WHO_AM_I, 1) of
73+
{ok, <<?LIS3DH_ID:8>>} ->
74+
console:puts("LIS3DH detected\n"),
75+
ok;
76+
{ok, <<Other:8>>} ->
77+
console:puts("Unexpected WHO_AM_I: "),
78+
console:puts(erlang:integer_to_list(Other, 16)),
79+
console:puts("\n"),
80+
{error, unexpected_id};
81+
{error, _} = Error ->
82+
Error
83+
end.
84+
85+
configure(I2C) ->
86+
%% CTRL_REG1: 50 Hz ODR, normal mode, X/Y/Z enabled
87+
%% Bits: ODR=0100 LPen=0 Zen=1 Yen=1 Xen=1 -> 0x47
88+
ok = i2c:write_bytes(I2C, ?LIS3DH_ADDR, ?CTRL_REG1, 16#47),
89+
%% CTRL_REG4: +/- 2g full scale, high resolution
90+
%% Bits: BDU=1 BLE=0 FS=00 HR=1 ST=00 SIM=0 -> 0x88
91+
ok = i2c:write_bytes(I2C, ?LIS3DH_ADDR, ?CTRL_REG4, 16#88).
92+
93+
loop(I2C) ->
94+
case read_acceleration(I2C) of
95+
{ok, {X, Y, Z}} ->
96+
console:puts("X="),
97+
console:puts(erlang:integer_to_list(X)),
98+
console:puts(" Y="),
99+
console:puts(erlang:integer_to_list(Y)),
100+
console:puts(" Z="),
101+
console:puts(erlang:integer_to_list(Z)),
102+
console:puts("\n");
103+
{error, Reason} ->
104+
console:puts("Read error: "),
105+
console:puts(erlang:atom_to_list(Reason)),
106+
console:puts("\n")
107+
end,
108+
timer:sleep(1000),
109+
loop(I2C).
110+
111+
read_acceleration(I2C) ->
112+
%% Read 6 bytes starting at OUT_X_L with auto-increment (bit 7 set)
113+
case i2c:read_bytes(I2C, ?LIS3DH_ADDR, ?OUT_X_L bor 16#80, 6) of
114+
{ok, <<XL:8, XH:8/signed, YL:8, YH:8/signed, ZL:8, ZH:8/signed>>} ->
115+
%% 12-bit left-justified in high-resolution mode: shift right by 4
116+
X = ((XH bsl 8) bor XL) bsr 4,
117+
Y = ((YH bsl 8) bor YL) bsr 4,
118+
Z = ((ZH bsl 8) bor ZL) bsr 4,
119+
{ok, {X, Y, Z}};
120+
{error, _} = Error ->
121+
Error
122+
end.

libs/avm_stm32/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ include(BuildErlang)
2424

2525
set(ERLANG_MODULES
2626
gpio
27+
i2c
2728
)
2829

2930
pack_archive(avm_stm32 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES})

0 commit comments

Comments
 (0)