Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu/sam0_common: Implement time-sharing of SERCOMs #21029

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

maribu
Copy link
Member

@maribu maribu commented Nov 21, 2024

Contribution description

This adds a periph_sercom feature and implementation which periph_i2c, periph_uart, and periph_spi are implemented on top. This allows for sharing a single SERCOM instance to provide multiple serial interfaces (in round-robin time-sharing fashion).

Note

In practice, a SERCOM can often not be shared if it needs to provide an UART.

Background:

While code using the I2C/SPI APIs is already optimized to share the peripheral with i2c_acquire()/spi_acquire() and i2c_release()/spi_release(), UARTs are typically not shared and most users will not call uart_poweron() and uart_poweroff() to only have the SERCOM in UART mode when actually needed. Worse: For many use cases (such as stdin), the UART will need to be constantly running, as receiving data happens asynchronously at unpredictable points in time.

Testing procedure

make BOARD=adafruit-metro-m4-express flash term -C tests/periph/selftest_shield

2024-11-21 20:29:42,410 # ALL TESTS SUCCEEDED
2024-11-21 20:29:42,395 # main(): This is RIOT! (Version: 2025.01-devel-156-g1baf0-cpu/sam0_common/periph_sercom)
2024-11-21 20:29:42,396 # self-testing peripheral drivers
2024-11-21 20:29:42,396 # ===============================
2024-11-21 20:29:42,396 # Starting test for GPIO at tests/periph/selftest_shield/main.c:350
2024-11-21 20:29:42,396 # [OK]
2024-11-21 20:29:42,397 # Starting test for GPIO at tests/periph/selftest_shield/main.c:372
2024-11-21 20:29:42,397 # [OK]
2024-11-21 20:29:42,397 # Starting test for GPIO at tests/periph/selftest_shield/main.c:402
2024-11-21 20:29:42,397 # [OK]
2024-11-21 20:29:42,397 # Starting test for GPIO at tests/periph/selftest_shield/main.c:432
2024-11-21 20:29:42,397 # (skipped)
2024-11-21 20:29:42,398 # Starting test for GPIO at tests/periph/selftest_shield/main.c:458
2024-11-21 20:29:42,398 # (skipped)
2024-11-21 20:29:42,398 # Starting test for GPIO-IRQ at tests/periph/selftest_shield/main.c:505
2024-11-21 20:29:42,398 # [OK]
2024-11-21 20:29:42,399 # Starting test for GPIO-IRQ at tests/periph/selftest_shield/main.c:571
2024-11-21 20:29:42,399 # [OK]
2024-11-21 20:29:42,399 # Starting test for GPIO-IRQ at tests/periph/selftest_shield/main.c:637
2024-11-21 20:29:42,399 # [OK]
2024-11-21 20:29:42,399 # Starting test for I2C at tests/periph/selftest_shield/main.c:711
2024-11-21 20:29:42,399 # [OK]
2024-11-21 20:29:42,400 # Starting test for UART at tests/periph/selftest_shield/main.c:806
2024-11-21 20:29:42,400 # [OK]
2024-11-21 20:29:42,400 # Starting test for UART at tests/periph/selftest_shield/main.c:815
2024-11-21 20:29:42,400 # [OK]
2024-11-21 20:29:42,400 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,401 # [OK]
2024-11-21 20:29:42,401 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,401 # [OK]
2024-11-21 20:29:42,401 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,401 # [OK]
2024-11-21 20:29:42,402 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,402 # [OK]
2024-11-21 20:29:42,402 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,402 # [OK]
2024-11-21 20:29:42,402 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,402 # [OK]
2024-11-21 20:29:42,403 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,403 # [OK]
2024-11-21 20:29:42,403 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,403 # [OK]
2024-11-21 20:29:42,404 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,404 # [OK]
2024-11-21 20:29:42,404 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,404 # [OK]
2024-11-21 20:29:42,404 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,405 # [OK]
2024-11-21 20:29:42,405 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,405 # [OK]
2024-11-21 20:29:42,405 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,405 # [OK]
2024-11-21 20:29:42,406 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,406 # [OK]
2024-11-21 20:29:42,406 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,406 # [OK]
2024-11-21 20:29:42,406 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,406 # [OK]
2024-11-21 20:29:42,407 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,407 # [OK]
2024-11-21 20:29:42,407 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,407 # [OK]
2024-11-21 20:29:42,408 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,408 # [OK]
2024-11-21 20:29:42,408 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,408 # [OK]
2024-11-21 20:29:42,408 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,408 # [OK]
2024-11-21 20:29:42,409 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,409 # [OK]
2024-11-21 20:29:42,409 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,409 # [OK]
2024-11-21 20:29:42,409 # Starting test for SPI at tests/periph/selftest_shield/main.c:858
2024-11-21 20:29:42,409 # [OK]
2024-11-21 20:29:42,410 # Starting test for ADC at tests/periph/selftest_shield/main.c:1072
2024-11-21 20:29:42,410 # [OK]
2024-11-21 20:29:42,410 # 
2024-11-21 20:29:42,410 # 
2024-11-21 20:29:42,410 # ALL TESTS SUCCEEDED
2024-11-21 20:29:42,410 # { "threads": [{ "name": "main", "stack_size": 1536, "stack_used": 572}]}

Issues/PRs references

None

@github-actions github-actions bot added Platform: ARM Platform: This PR/issue effects ARM-based platforms Area: tests Area: tests and testing framework Area: build system Area: Build system Area: boards Area: Board ports labels Nov 21, 2024
@maribu maribu added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Nov 21, 2024
@github-actions github-actions bot added the Area: cpu Area: CPU/MCU ports label Nov 21, 2024
@maribu maribu changed the title Cpu/sam0 common/periph sercom cpu/sam0_common: Implement time-sharing of SERCOMs Nov 21, 2024
@riot-ci
Copy link

riot-ci commented Nov 21, 2024

Murdock results

FAILED

f5abef9 boards/adafruit-metro-m4-express: make use of SERCOM sharing

Success Failures Total Runtime
1086 0 9357 02m:46s

Artifacts

@maribu maribu force-pushed the cpu/sam0_common/periph_sercom branch from 1baf069 to f9c952d Compare November 21, 2024 21:42
cpu/sam0_common/periph/sercom.c Show resolved Hide resolved
cpu/sam0_common/periph/sercom.c Outdated Show resolved Hide resolved
@@ -281,55 +248,47 @@ static void _spi_acquire(spi_t bus, spi_mode_t mode, spi_clk_t clk)
* to mitigate the rounding error due to integer arithmetic, the
* equation is modified to
* BAUD.reg = ((f_ref + f_bus) / (2 * f_bus) - 1) */
const uint8_t baud = (gclk_src + clk) / (2 * clk) - 1;
uint8_t baud = (gclk_src + clk) / (2 * clk) - 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you unconst this (line seems uncanged otherwise)
same for ctrla

liter (const correctness) might not like that change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the other variables holding the values to store into the registers.

@maribu maribu force-pushed the cpu/sam0_common/periph_sercom branch from 56ef1ea to 08a71df Compare November 26, 2024 20:51
@github-actions github-actions bot added Area: drivers Area: Device drivers Platform: ESP Platform: This PR/issue effects ESP-based platforms and removed Area: tests Area: tests and testing framework labels Nov 26, 2024
@maribu maribu force-pushed the cpu/sam0_common/periph_sercom branch from 08a71df to b2bb805 Compare November 26, 2024 20:57
This adds a `periph_sercom` feature and implementation which
`periph_i2c`, `periph_uart`, and `periph_spi` are implemented on top.
This allows for sharing a single SERCOM instance to provide multiple
serial interfaces (in round-robin time-sharing fashion).

Note: In practice, a SERCOM can often not be shared if it needs to
      provide an UART.

Background:

While code using the I2C/SPI APIs is already optimized to share the
peripheral with `i2c_acquire()`/`spi_acquire()` and
`i2c_release()`/`spi_release()`, UARTs are typically not shared and most
users will not call `uart_poweron()` and `uart_poweroff()` to only have
the SERCOM in UART mode when actually needed. Worse: For many use cases
(such as stdin), the UART will need to be constantly running, as
receiving data happens asynchronously at unpredictable points in time.
For `gpio_ll_print_conf()` we need to include `<stdio.h>`, when not
using `fmt.h`.
Now that time-sharing SERCOMs is possible, we can provide the SPI on
D11/D12/D13 (backed by SERCOM3) also when the UART on D0/D1 (also backed
by SERCOM3) is used.
@maribu maribu force-pushed the cpu/sam0_common/periph_sercom branch from b2bb805 to f5abef9 Compare November 26, 2024 22:30
Comment on lines +89 to +107
#if defined(CPU_COMMON_SAMD21)
PM->APBCMASK.reg |= (PM_APBCMASK_SERCOM0 << sercom);
#elif defined (CPU_COMMON_SAMD5X)
if (sercom < 2) {
MCLK->APBAMASK.reg |= (1 << (sercom + 12));
} else if (sercom < 4) {
MCLK->APBBMASK.reg |= (1 << (sercom + 7));
} else {
MCLK->APBDMASK.reg |= (1 << (sercom - 4));
}
#else
if (sercom < 5) {
MCLK->APBCMASK.reg |= (MCLK_APBCMASK_SERCOM0 << sercom);
}
#if defined(CPU_COMMON_SAML21)
else {
MCLK->APBDMASK.reg |= (MCLK_APBDMASK_SERCOM5);
}
#endif /* CPU_COMMON_SAML21 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you basically have an #ifdef case for each CPU family, I think it would be more readable to move _sercom_apb_enable() to periph_cpu.h of the respective CPU family memember.

So you'd only be left with

static inline void _sercom_apb_enable(sercom_t sercom)
{
    PM->APBCMASK.reg |= PM_APBCMASK_SERCOM0 << sercom;
}

on samd21 and

static inline void _sercom_apb_enable(sercom_t sercom)
{
    MCLK->APBCMASK.reg |= MCLK_APBCMASK_SERCOM0 << sercom;
}

on saml1x, etc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: boards Area: Board ports Area: build system Area: Build system Area: cpu Area: CPU/MCU ports Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Platform: ARM Platform: This PR/issue effects ARM-based platforms Platform: ESP Platform: This PR/issue effects ESP-based platforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants