Skip to content

Commit db87126

Browse files
committed
Use official STM32 SDK for stm32 builds instead of libopencm3
AtomVM now supports the following STM32 families: - stm32f2xx - stm32f4xx (was supported by libopencm3) - stm32f7xx (was supported by libopencm3) - stm32g0xx - stm32g4xx - stm32h5xx - stm32h7xx - stm32l4xx - stm32l5xx - stm32u3xx - stm32wbxx Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent b57b94f commit db87126

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+4426
-828
lines changed

.github/workflows/stm32-build.yaml

Lines changed: 131 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ on:
1414
- 'CMakeModules/**'
1515
- 'src/platforms/stm32/**'
1616
- 'src/libAtomVM/**'
17+
- 'libs/**'
1718
pull_request:
1819
paths:
1920
- '.github/workflows/stm32-build.yaml'
2021
- 'CMakeLists.txt'
2122
- 'CMakeModules/**'
2223
- 'src/platforms/stm32/**'
2324
- 'src/libAtomVM/**'
25+
- 'libs/**'
2426

2527
concurrency:
2628
group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/main' && github.ref || github.run_id }}
@@ -34,17 +36,63 @@ jobs:
3436
contents: read
3537
security-events: write
3638

37-
steps:
38-
- uses: actions/cache@v4
39-
id: builddeps-cache
40-
with:
41-
path: |
42-
/home/runner/libopencm3
43-
key: ${{ runner.os }}-build-deps
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
device:
43+
- stm32f411ceu6
44+
- stm32h743vit6
45+
- stm32u585ait6q
46+
- stm32wb55rg
47+
- stm32h562rgt6
48+
- stm32f746zgt6
49+
- stm32g474ret6
50+
- stm32l476rgt6
51+
- stm32l562qei6
52+
- stm32f207zgt6
53+
- stm32u375rgt6
54+
- stm32g0b1ret6
55+
include:
56+
- device: stm32f411ceu6
57+
max_size: 393216
58+
renode_platform: stm32f4.repl
59+
avm_address: "0x08060000"
60+
- device: stm32h743vit6
61+
max_size: 524288
62+
renode_platform: stm32h743.repl
63+
avm_address: "0x08080000"
64+
- device: stm32u585ait6q
65+
max_size: 524288
66+
- device: stm32wb55rg
67+
max_size: 524288
68+
- device: stm32h562rgt6
69+
max_size: 524288
70+
- device: stm32f746zgt6
71+
max_size: 524288
72+
renode_platform: stm32f746.repl
73+
avm_address: "0x08080000"
74+
- device: stm32g474ret6
75+
max_size: 393216
76+
- device: stm32l476rgt6
77+
max_size: 524288
78+
- device: stm32l562qei6
79+
max_size: 393216
80+
renode_platform: stm32l562.repl
81+
avm_address: "0x08060000"
82+
- device: stm32f207zgt6
83+
max_size: 524288
84+
- device: stm32u375rgt6
85+
max_size: 524288
86+
- device: stm32g0b1ret6
87+
max_size: 393216
88+
renode_platform: stm32g0b1.repl
89+
avm_address: "0x08060000"
4490

91+
steps:
4592
- uses: erlef/setup-beam@v1
4693
with:
47-
otp-version: "27"
94+
otp-version: "28"
95+
rebar3-version: "3.25.1"
4896
hexpm-mirrors: |
4997
https://builds.hex.pm
5098
https://repo.hex.pm
@@ -54,42 +102,91 @@ jobs:
54102
run: sudo apt update
55103

56104
- name: "Install deps"
57-
run: sudo apt install -y cmake gperf gcc-arm-none-eabi
105+
run: sudo apt install -y cmake ninja-build gperf python3-pip && pip3 install meson
58106

59-
- name: Checkout and build libopencm3
60-
if: ${{ steps.builddeps-cache.outputs.cache-hit != 'true' }}
61-
working-directory: /home/runner
107+
- name: "Install ARM GNU Toolchain"
62108
run: |
63-
set -euo pipefail
64-
cd /home/runner
65-
test -d libopencm3 && rm -fr libopencm3
66-
git clone https://github.com/libopencm3/libopencm3.git -b v0.8.0
67-
cd libopencm3
68-
make
109+
wget -q https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz
110+
tar xJf arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz -C /opt
111+
echo "/opt/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi/bin" >> $GITHUB_PATH
69112
70113
- name: Checkout repo
71114
uses: actions/checkout@v4
72115

73-
- name: "Git config safe.directory for codeql"
74-
run: git config --global --add safe.directory /__w/AtomVM/AtomVM
75-
76-
- name: "Initialize CodeQL"
77-
uses: github/codeql-action/init@v4
78-
with:
79-
languages: 'cpp'
80-
build-mode: manual
81-
queries: +./code-queries/term-to-non-term-func.ql,./code-queries/non-term-to-term-func.ql,./code-queries/mismatched-atom-string-length.ql,./code-queries/mismatched-free-type.ql,./code-queries/term-use-after-gc.ql
82-
83-
- name: Build
116+
- name: "Build for ${{ matrix.device }}"
84117
shell: bash
85118
working-directory: ./src/platforms/stm32/
86119
run: |
87120
set -euo pipefail
88121
mkdir build
89122
cd build
90-
# -DAVM_WARNINGS_ARE_ERRORS=ON
91-
cmake .. -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake -DLIBOPENCM3_DIR=/home/runner/libopencm3
92-
make -j
123+
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake -DDEVICE=${{ matrix.device }}
124+
cmake --build .
125+
126+
- name: "Check firmware size for ${{ matrix.device }}"
127+
shell: bash
128+
working-directory: ./src/platforms/stm32/build
129+
run: |
130+
set -euo pipefail
131+
ELF="AtomVM-${{ matrix.device }}.elf"
132+
SIZES=$(arm-none-eabi-size "$ELF" | tail -1)
133+
TEXT=$(echo "$SIZES" | awk '{print $1}')
134+
DATA=$(echo "$SIZES" | awk '{print $2}')
135+
FLASH_USED=$((TEXT + DATA))
136+
MAX=${{ matrix.max_size }}
137+
echo "Firmware flash usage: ${FLASH_USED} bytes ($(( FLASH_USED / 1024 )) KB)"
138+
echo "Flash limit: ${MAX} bytes ($(( MAX / 1024 )) KB)"
139+
if [ "$FLASH_USED" -gt "$MAX" ]; then
140+
echo "::error::Firmware too large: ${FLASH_USED} bytes exceeds ${MAX} byte limit for ${{ matrix.device }}"
141+
exit 1
142+
fi
143+
echo "OK: ${FLASH_USED} / ${MAX} bytes ($(( FLASH_USED * 100 / MAX ))% used)"
144+
145+
- name: Build host AtomVM and test AVMs
146+
if: matrix.renode_platform
147+
run: |
148+
set -euo pipefail
149+
mkdir build-host
150+
cd build-host
151+
cmake .. -G Ninja
152+
cmake --build . -t stm32_boot_test stm32_gpio_test
153+
154+
- name: Install Renode
155+
if: matrix.renode_platform
156+
run: |
157+
set -euo pipefail
158+
mkdir -p renode-portable
159+
wget -qO- https://builds.renode.io/renode-latest.linux-portable.tar.gz \
160+
| tar -xzf - -C renode-portable --strip-components=1
161+
echo "$PWD/renode-portable" >> $GITHUB_PATH
162+
pip install -r renode-portable/tests/requirements.txt
93163
94-
- name: "Perform CodeQL Analysis"
95-
uses: github/codeql-action/analyze@v4
164+
- name: Run Renode boot test
165+
if: matrix.renode_platform
166+
run: |
167+
LOCAL_REPL="src/platforms/stm32/tests/renode/${{ matrix.renode_platform }}"
168+
if [ -f "$LOCAL_REPL" ]; then
169+
PLATFORM="@$PWD/$LOCAL_REPL"
170+
else
171+
PLATFORM="@platforms/cpus/${{ matrix.renode_platform }}"
172+
fi
173+
renode-test src/platforms/stm32/tests/renode/stm32_boot_test.robot \
174+
--variable ELF:@$PWD/src/platforms/stm32/build/AtomVM-${{ matrix.device }}.elf \
175+
--variable AVM:@$PWD/build-host/src/platforms/stm32/tests/test_erl_sources/stm32_boot_test.avm \
176+
--variable AVM_ADDRESS:${{ matrix.avm_address }} \
177+
--variable PLATFORM:$PLATFORM
178+
179+
- name: Run Renode GPIO test
180+
if: matrix.renode_platform
181+
run: |
182+
LOCAL_REPL="src/platforms/stm32/tests/renode/${{ matrix.renode_platform }}"
183+
if [ -f "$LOCAL_REPL" ]; then
184+
PLATFORM="@$PWD/$LOCAL_REPL"
185+
else
186+
PLATFORM="@platforms/cpus/${{ matrix.renode_platform }}"
187+
fi
188+
renode-test src/platforms/stm32/tests/renode/stm32_gpio_test.robot \
189+
--variable ELF:@$PWD/src/platforms/stm32/build/AtomVM-${{ matrix.device }}.elf \
190+
--variable AVM:@$PWD/build-host/src/platforms/stm32/tests/test_erl_sources/stm32_gpio_test.avm \
191+
--variable AVM_ADDRESS:${{ matrix.avm_address }} \
192+
--variable PLATFORM:$PLATFORM

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7070
- Added initial support for ESP32C5 and ESP32C61
7171
- Added `Range:size/1`
7272
- Added missing `ledc` functions for esp32 platform
73+
- Added support for 10 new STM32 families by switching to STM32 official SDK
7374

7475
### Changed
7576

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ add_subdirectory(tools/uf2tool)
8686
if (NOT "${CMAKE_GENERATOR}" MATCHES "Xcode")
8787
add_custom_target(dialyzer COMMENT "Run dialyzer")
8888
add_subdirectory(libs)
89+
add_subdirectory(src/platforms/stm32/tests/test_erl_sources)
8990
if(NOT AVM_BUILD_RUNTIME_ONLY)
9091
add_subdirectory(examples)
9192
add_subdirectory(doc)

README.Md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Supported Platforms
1717

1818
* Linux, macOS, FreeBSD, DragonFly ([generic_unix](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-on-the-generic-unix-platform))
1919
* ESP32 SoC (with IDF/FreeRTOS, see [esp32](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-on-the-esp32-platform))
20-
* STM32 MCUs (with LibOpenCM3, see [stm32](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-on-the-stm32-platform))
20+
* STM32 MCUs (with official ST HAL/LL SDK, see [stm32](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-on-the-stm32-platform))
2121
* Raspberry Pi Pico and Pico 2 (see [rp2](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-on-the-raspberry-pi-pico-platform))
2222
* Browsers and NodeJS with WebAssembly (see [emscripten](https://doc.atomvm.org/main/getting-started-guide.html#getting-started-with-atomvm-webassembly))
2323

doc/src/build-instructions.md

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -707,39 +707,21 @@ To add support for a new peripheral or protocol using an AtomVM port, you need t
707707

708708
## Building for STM32
709709

710+
This section describes building AtomVM using the official ST HAL/LL SDK, which is downloaded automatically via CMake FetchContent. This platform supports STM32F2, STM32F4, STM32F7, STM32G0, STM32G4, STM32H5, STM32H7, STM32L4, STM32L5, STM32U3, STM32U5, and STM32WB families.
711+
710712
### STM32 Prerequisites
711713

712714
The following software is required to build AtomVM for the STM32 platform:
713715

714-
* [11.3 ARM toolchain](https://developer.arm.com/-/media/Files/downloads/gnu/11.3.rel1/binrel/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi.tar.xz) (or compatible with your system)
715-
* [libopencm3](https://github.com/libopencm3/libopencm3.git) version 0.8.0
716-
* `cmake`
717-
* `make`
716+
* ARM toolchain (`arm-none-eabi-gcc`, e.g. 11.3 or later)
717+
* `cmake` (3.13 or later)
718+
* `meson`
719+
* `ninja`
718720
* `git`
719-
* `python`
720721
* Erlang/OTP `escript`
721722

722723
```{note}
723-
AtomVM tests this build on the latest Ubuntu github runner.
724-
```
725-
726-
### Setup libopencm3
727-
728-
Before building for the first time you need to have a compiled clone of the libopencm3 libraries, from inside the AtomVM/src/platforms/stm32 directory:
729-
730-
```shell
731-
$ git clone -b v0.8.0 https://github.com/libopencm3/libopencm3.git
732-
$ cd libopencm3 && make -j4 && cd ..
733-
```
734-
735-
```{tip}
736-
You can put libopencm3 wherever you want on your PC as long as you update LIBOPENCM3_DIR to point to it. This
737-
example assumes it has been cloned into /opt/libopencm3 and built. From inside the AtomVM/src/platforms/stm32
738-
directory:
739-
740-
$ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake \
741-
-DLIBOPENCM3_DIR=/opt/libopencm3 ..
742-
724+
No external SDK download is required. The STM32 HAL/LL drivers and CMSIS headers are fetched automatically by CMake during the build. AtomVM is built with `picolibc` which is also downloaded as part of the build and requires `meson` and `ninja`.
743725
```
744726

745727
### Build AtomVM with cmake toolchain file
@@ -749,14 +731,32 @@ $ cd <atomvm-source-tree-root>
749731
$ cd src/platforms/stm32
750732
$ mkdir build
751733
$ cd build
752-
$ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake ..
753-
$ make
734+
$ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake -DDEVICE=stm32f411ceu6 ..
735+
$ ninja
754736
```
755737

756738
### Changing the target device
757739

758-
The default build is based on the STM32F4Discovery board chip (`stm32f407vgt6`). If you want to target a different
759-
chip, pass the `-DDEVICE` flag when invoking cmake. For example, to use the BlackPill V2.0, pass `-DDEVICE=stm32f411ceu6`. At this time any `STM32F4` or `STM32F7` device with 512KB or more of on package flash should work with AtomVM. If an unsupported device is passed with the `DEVICE` parameter the configuration will fail. For devices with either 512KB or 768KB of flash the available application flash space will be limited to 128KB. Devices with only 512KB of flash may also suffer from slightly reduced performance because the compiler must optimize for size rather than performance.
740+
The default build targets the BlackPill (`stm32f411ceu6`). Pass the `-DDEVICE` flag to select a different device. Supported families and example devices:
741+
742+
| Family | Example Devices | Max Clock |
743+
|--------|----------------|-----------|
744+
| STM32F2 | `stm32f205rgt6`, `stm32f207zgt6` | 120 MHz |
745+
| STM32F4 | `stm32f411ceu6`, `stm32f401ceu6`, `stm32f407vgt6`, `stm32f429zit6` | 84-180 MHz |
746+
| STM32F7 | `stm32f746zgt6`, `stm32f767zit6` | 216 MHz |
747+
| STM32G0 | `stm32g0b1ret6` | 64 MHz |
748+
| STM32G4 | `stm32g474ret6`, `stm32g491ret6` | 170 MHz |
749+
| STM32H5 | `stm32h562rgt6` | 250 MHz |
750+
| STM32H7 | `stm32h743vit6` | 480 MHz |
751+
| STM32L4 | `stm32l476rgt6`, `stm32l4r5zit6` | 80-120 MHz |
752+
| STM32L5 | `stm32l552ret6` | 110 MHz |
753+
| STM32U3 | `stm32u375rgt6`, `stm32u385rgt6` | 96 MHz |
754+
| STM32U5 | `stm32u585ait6q` | 160 MHz |
755+
| STM32WB | `stm32wb55rg` | 64 MHz |
756+
757+
If an unsupported device is passed with the `DEVICE` parameter the configuration will fail.
758+
759+
For devices with 512KB or less of flash, application flash space will be limited and the compiler optimizes for size rather than performance.
760760

761761
```{attention}
762762
For devices with only 512KB of flash the application address is different and must be adjusted when flashing your
@@ -766,7 +766,9 @@ devices is `0x8060000`.
766766

767767
### Configuring the Console
768768

769-
The default build for any `DEVICE` will use `USART2` and output will be on `PA2`. This default will work well for most `Discovery` and generic boards that do not have an on-board TTL to USB-COM support (including the `stm32f411ceu6` A.K.A. `BlackPill V2.0`). For `Nucleo` boards that do have on board UART to USB-COM support you may pass the `cmake` parameter `-DBOARD=nucleo` to have the correct USART and TX pins configured automatically. The `Nucleo-144` series use `USART3` and `PD8`, while the supported `Nucleo-64` boards use `USART2`, but passing the `BOARD` parameter along with `DEVICE` will configure the correct `USART` for your model. If any other boards are discovered to have on board USB UART support pull requests, or opening issues with the details, are more than welcome.
769+
By default, stdout and stderr are printed on the configured console USART. Baudrate is 115200 and serial transmission is 8N1 with no flow control.
770+
771+
The default console is `USART1` on `PA9`. For `Nucleo` boards, pass `-DBOARD=nucleo` to automatically select the correct USART for your board.
770772

771773
Example to configure a `NUCLEO-F429ZI`:
772774

@@ -777,16 +779,44 @@ $ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake -DDEVICE=stm32f429zi
777779

778780
The AtomVM system console `USART` may also be configured to a specific uart peripheral. Pass one of the parameters from the chart below with the `cmake` option `-DAVM_CFG_CONSOLE=CONSOLE_#`, using the desired console parameter in place of `CONSOLE_#`. Not all UARTs are available on every supported board, but most will have several options that are not already used by other on board peripherals. Consult your data sheets for your device to select an appropriate console.
779781

780-
| Parameter | USART | TX Pin | AtomVM Default | Nucleo-144 | Nucleo-64 |
781-
|-----------|-------|--------|----------------|------------|-----------|
782-
| `CONSOLE_1` | `USART1` | `PA9` | | | |
783-
| `CONSOLE_2` | `USART2` | `PA2` | | ||
782+
| Parameter | USART | TX Pin | Default | Nucleo-144 | Nucleo-64 |
783+
|-----------|-------|--------|---------|------------|-----------|
784+
| `CONSOLE_1` | `USART1` | `PA9` | | | |
785+
| `CONSOLE_2` | `USART2` | `PA2` | | ||
784786
| `CONSOLE_3` | `USART3` | `PD8` | || |
785787
| `CONSOLE_4` | `UART4` | `PC10` | | | |
786788
| `CONSOLE_5` | `UART5` | `PC12` | | | |
787789
| `CONSOLE_6` | `USART6` | `PC6` | | | |
788-
| `CONSOLE_7` | `UART7` | `PF7` | | | |
789-
| `CONSOLE_8` | `UART8` | `PJ8` | | | |
790+
791+
### Configuring the HSE frequency
792+
793+
The external oscillator (HSE) frequency varies between boards. Set it at build time with `-DAVM_HSE_VALUE=<freq_hz>`:
794+
795+
```shell
796+
$ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake -DDEVICE=stm32f407vgt6 \
797+
-DAVM_HSE_VALUE=8000000 ..
798+
```
799+
800+
Default HSE values per family (set in the family HAL configuration header):
801+
802+
| Family | Default HSE | Typical Boards |
803+
|--------|-------------|----------------|
804+
| STM32F2 | 8 MHz | Nucleo boards |
805+
| STM32F4 | 25 MHz | BlackPill boards |
806+
| STM32F7 | 8 MHz | Nucleo boards (ST-Link MCO bypass) |
807+
| STM32G0 | 8 MHz | |
808+
| STM32G4 | 8 MHz | Nucleo boards |
809+
| STM32H5 | 8 MHz | Nucleo boards (ST-Link MCO bypass), WeAct Studio H562 |
810+
| STM32H7 | 8 MHz | Nucleo boards (ST-Link MCO bypass), WeAct Studio H743 (25 MHz) |
811+
| STM32L4 | 8 MHz | Nucleo boards |
812+
| STM32L5 | N/A (uses MSI) | Nucleo boards |
813+
| STM32U3 | 16 MHz | Nucleo boards |
814+
| STM32U5 | 16 MHz | Nucleo-U585AI-Q, WeAct Studio U585 (25 MHz) |
815+
| STM32WB | 32 MHz | Nucleo-WB55, WeAct Studio WB55 (required for BLE radio) |
816+
817+
```{note}
818+
Not all STM32 families have been tested on hardware. The F4, H5, H7, U5, and WB families have been tested on actual boards. The F2, F7, G0, G4, L4, L5, and U3 families are supported in the build system but have not yet been validated on hardware. If you encounter issues with an untested family, please open an [issue on GitHub](https://github.com/atomvm/AtomVM/issues).
819+
```
790820

791821
### Configure STM32 logging with `cmake`
792822

@@ -803,19 +833,6 @@ For log entries colorized by log level pass `-DAVM_ENABLE_LOG_COLOR=on` to cmake
803833

804834
By default only `ERROR` messages contain file and line number information. This can be included with all log entries by passing `-DAVM_ENABLE_LOG_LINES=on` to cmake, but it does incur a significant performance penalty and is only suggested for debugging during development.
805835

806-
### Console Printing on STM32
807-
808-
AtomVM is built with standard `newlib` to support `long long` integers (`signed` and `unsigned`). If you are building for a device with extremely limited flash space the `nano` version of `newlib` can be used instead. This may be done by passing `-DAVM_NEWLIB_NANO=on`. If the `nano newlib` is used logs will be automatically disabled, this is because many of the VM low level log messages will include `%ull` formatting and will cause buffer overflows and crash the VM if logging is not disabled for `nano newlib` builds. The total flash savings of using `nano newlib` and disabling logs is just under 40kB.
809-
810-
By default, stdout and stderr are printed on USART2. On the STM32F4Discovery board, you can see them
811-
using a TTL-USB with the TX pin connected to board's pin PA2 (USART2 RX). Baudrate is 115200 and serial transmission
812-
is 8N1 with no flow control.
813-
814-
```{seealso}
815-
If building for a different target USART may be configure as explained above in
816-
[Configuring the Console](#configuring-the-console).
817-
```
818-
819836
### Configuring deployment builds for STM32
820837

821838
After your application has been tested (_and debugged_) and is ready to put into active use you may want to tune the build of AtomVM. For instance disabling logging with `-DAVM_LOG_DISABLE=on` as a `cmake` configuration option may result in slightly better performance. This will have no affect on the console output of your application, just disable low level log messages from the AtomVM system. You may also want to enabling automatic reboot in the case that your application ever exits with a return other than `ok`. This can be enabled with the `cmake` option `-DAVM_CONFIG_REBOOT_ON_NOT_OK=on`.

0 commit comments

Comments
 (0)