Skip to content

Commit 45e8319

Browse files
committed
Add esp32 uart echo example
1 parent 2e02598 commit 45e8319

File tree

12 files changed

+446
-2
lines changed

12 files changed

+446
-2
lines changed

.github/workflows/build-esp.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
example: [esp32-led-blink-sdk, esp32-led-strip-sdk]
22+
example: [esp32-led-blink-sdk, esp32-led-strip-sdk, esp32-uart-echo]
2323

2424
steps:
2525
- name: Checkout repo

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ build/
88
*/sdkconfig.old
99
*/managed_components
1010
*/dependencies.lock
11-
11+
*/.cache/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Each example in this repository contains build and deployment instructions, howe
2222
| ---- | -------- | --- | ----------- | ----- |
2323
| [esp32-led-blink-sdk](./esp32-led-blink-sdk) | ESP32-C6-Bug | ESP-IDF SDK | Blink an LED repeatedly with Swift & the ESP-IDF. | <img width="300" src="esp32-led-blink-sdk/assets/images/ledon.jpg"> |
2424
| [esp32-led-strip-sdk](./esp32-led-strip-sdk) | ESP32-C6-DevKitC-1 | ESP-IDF SDK | Control NeoPixel LEDs with Swift & the ESP-IDF. | <img width="300" src="https://github.com/swiftlang/swift-embedded-examples/assets/1186214/15f8a3e0-953e-426d-ad2d-3902baf859be"> |
25+
| [esp32-uart-echo](./esp32-uart-echo) | ESP32-C6 | ESP-IDF SDK | UART echo example using Swift's `print()` and `readLine()`. | |
2526
| [harmony](./harmony) | Raspberry Pi Pico W | Pico SDK | A bluetooth speaker and ferrofluidic music visualizer. Firmware, Electrical, and Mechanical designs fully available. | <img width="300" src="harmony/assets/hero.jpg"> |
2627
| [nrfx-blink-sdk](./nrfx-blink-sdk) | nRF52840-DK | Zephyr SDK | Blink an LED repeatedly with Swift & Zephyr. | <img width="300" src="https://github.com/swiftlang/swift-embedded-examples/assets/1186214/ae3ff153-dd33-4460-8a08-4eac442bf7b0"> |
2728
| [nuttx-riscv-blink](./nuttx-riscv-blink) | QEMU | NuttX | Blink a virualized led in QEMU using the Apache NuttX RTOS | |

esp32-uart-echo/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
cmake_minimum_required(VERSION 3.29)
2+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
3+
idf_build_set_property(MINIMAL_BUILD ON)
4+
project(main)

esp32-uart-echo/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# esp32-uart-echo
2+
3+
This example demonstrates how to use the ESP-IDF UART driver from Swift to create a simple echo application. It wraps the ESP-IDF UART APIs in a Swift-friendly `Uart` struct and redirects Swift's standard I/O (`print()` and `readLine()`) to a custom UART port. This example is specifically made for the RISC-V MCUs from Espressif (the Xtensa MCUs are not currently supported by Swift).
4+
5+
## Features
6+
7+
- Swift wrapper around ESP-IDF UART driver APIs
8+
- Redirect `stdin`/`stdout`/`stderr` to a custom UART port
9+
- Use Swift's native `readLine()` and `print()` for UART communication
10+
- Simple echo functionality: receives data and sends it back
11+
12+
## Requirements
13+
14+
- Set up the [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/) development environment. Follow the steps in the [ESP32-C6 "Get Started" guide](https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32c6/get-started/index.html).
15+
- Make sure you specifically set up development for RISC-V based Espressif chips, and not the Xtensa based products.
16+
17+
- Before trying to use Swift with the ESP-IDF SDK, make sure your environment works and can build the provided C/C++ sample projects, in particular:
18+
- Try building and running the "get-started/hello_world" example from ESP-IDF written in C.
19+
20+
## Building
21+
22+
- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support.
23+
- If needed, run export.sh to get access to the idf.py script from ESP-IDF.
24+
- Specify the target board type by using `idf.py set-target`. Any RISC-V based Espressif chip is supported.
25+
26+
```console
27+
$ cd esp32-uart-echo
28+
$ . <path-to-esp-idf>/export.sh
29+
$ idf.py set-target esp32c6 # or esp32c3, esp32p4, etc.
30+
$ idf.py build
31+
```
32+
33+
## Running
34+
35+
This example uses ESP32-C6 as a reference, but any RISC-V based Espressif chip can be used. Adjust the GPIO pins according to your development kit.
36+
37+
- Connect your board over a USB cable to your computer.
38+
- Connect a USB-UART converter to the configured UART pins (example for ESP32-C6):
39+
- TX (GPIO20) -> RX on USB-UART converter
40+
- RX (GPIO19) -> TX on USB-UART converter
41+
- GND -> GND on USB-UART converter
42+
- Use `idf.py` to upload the firmware:
43+
44+
```console
45+
$ idf.py flash
46+
```
47+
48+
- Open a serial terminal (e.g., `picocom`, `minicom`, or `screen`) connected to the USB-UART converter at 115200 baud:
49+
50+
```console
51+
$ picocom -b 115200 /dev/ttyUSB0
52+
```
53+
54+
- Type text and press Enter. The text will be echoed back to you.
55+
56+
## Configuration
57+
58+
The default UART configuration in `Main.swift`:
59+
60+
- **Port**: UART1
61+
- **TX Pin**: GPIO20
62+
- **RX Pin**: GPIO19
63+
- **Baud Rate**: 115200
64+
65+
To change these settings, modify the `Uart` initialization in `Main.swift`:
66+
67+
```swift
68+
let uart = Uart(portNum: 1, txPin: 20, rxPin: 19, baudRate: 115200)
69+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef BRIDGING_HEADER_H
13+
#define BRIDGING_HEADER_H
14+
15+
#include <stdio.h>
16+
#include <string.h>
17+
18+
#include "sdkconfig.h"
19+
#include "driver/uart.h"
20+
#include "driver/uart_vfs.h"
21+
22+
// Wrapper for uart_set_pin that works across ESP-IDF versions
23+
static inline esp_err_t set_uart_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num) {
24+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
25+
return _uart_set_pin6(uart_num, tx_io_num, rx_io_num, rts_io_num, cts_io_num, -1, -1);
26+
#else
27+
return uart_set_pin(uart_num, tx_io_num, rx_io_num, rts_io_num, cts_io_num);
28+
#endif
29+
}
30+
31+
// Helper functions to access stdin/stdout/stderr (which are macros in C)
32+
static inline void set_stdin(FILE *f) {
33+
stdin = f;
34+
}
35+
36+
static inline void set_stdout(FILE *f) {
37+
stdout = f;
38+
}
39+
40+
static inline void set_stderr(FILE *f) {
41+
stderr = f;
42+
}
43+
44+
static inline FILE* get_stdout(void) {
45+
return stdout;
46+
}
47+
48+
static inline FILE* get_stdin(void) {
49+
return stdin;
50+
}
51+
52+
#endif /* BRIDGING_HEADER_H */
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Register the app as an IDF component
2+
idf_component_register(
3+
SRCS /dev/null # We don't have any C++ sources
4+
PRIV_INCLUDE_DIRS "."
5+
LDFRAGMENTS "linker.lf"
6+
PRIV_REQUIRES esp_driver_uart vfs
7+
)
8+
9+
idf_build_get_property(target IDF_TARGET)
10+
idf_build_get_property(arch IDF_TARGET_ARCH)
11+
12+
if("${arch}" STREQUAL "xtensa")
13+
message(FATAL_ERROR "Not supported target: ${target}")
14+
endif()
15+
16+
# Extract -march and -mabi flags
17+
set(march_flag "")
18+
set(mabi_flag "")
19+
20+
# Method 1: Read from IDF's cflags response file (IDF 6.0+)
21+
if(DEFINED IDF_TOOLCHAIN_BUILD_DIR AND EXISTS "${IDF_TOOLCHAIN_BUILD_DIR}/cflags")
22+
file(STRINGS "${IDF_TOOLCHAIN_BUILD_DIR}/cflags" cflags_lines)
23+
foreach(line IN LISTS cflags_lines)
24+
if(line MATCHES "^-march=")
25+
set(march_flag "${line}")
26+
elseif(line MATCHES "^-mabi=")
27+
set(mabi_flag "${line}")
28+
endif()
29+
endforeach()
30+
endif()
31+
32+
# Method 2: Fallback to parsing CMAKE_C_FLAGS directly (older IDF versions)
33+
if(NOT march_flag OR NOT mabi_flag)
34+
string(REGEX MATCH "-march=[^ ]+" march_flag_fallback "${CMAKE_C_FLAGS}")
35+
string(REGEX MATCH "-mabi=[^ ]+" mabi_flag_fallback "${CMAKE_C_FLAGS}")
36+
if(NOT march_flag AND march_flag_fallback)
37+
set(march_flag "${march_flag_fallback}")
38+
endif()
39+
if(NOT mabi_flag AND mabi_flag_fallback)
40+
set(mabi_flag "${mabi_flag_fallback}")
41+
endif()
42+
endif()
43+
44+
# Default mabi if not found
45+
if(NOT mabi_flag)
46+
set(mabi_flag "-mabi=ilp32")
47+
endif()
48+
49+
# Strip Espressif custom extensions not supported by Swift
50+
if(march_flag)
51+
string(REGEX REPLACE "_x[^ ]*" "" march_flag "${march_flag}")
52+
endif()
53+
54+
# Clear the default COMPILE_OPTIONS which include a lot of C/C++ specific compiler flags that the Swift compiler will not accept
55+
get_target_property(var ${COMPONENT_LIB} COMPILE_OPTIONS)
56+
set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILE_OPTIONS "")
57+
58+
# Compute -Xcc flags to set up the C and C++ header search paths for Swift (for bridging header).
59+
set(SWIFT_INCLUDES)
60+
foreach(dir ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
61+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
62+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
63+
endforeach()
64+
foreach(dir ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
65+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
66+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
67+
endforeach()
68+
69+
# Swift compiler flags to build in Embedded Swift mode, optimize for size, choose the right ISA, ABI, etc.
70+
target_compile_options(${COMPONENT_LIB} PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:SHELL:
71+
-target riscv32-none-none-eabi
72+
-Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library -Osize
73+
-Xcc ${march_flag} -Xcc ${mabi_flag} -Xcc -fno-pic -Xcc -fno-pie
74+
75+
-pch-output-dir /tmp
76+
-Xfrontend -enable-single-module-llvm-emission
77+
78+
${SWIFT_INCLUDES}
79+
80+
-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
81+
>")
82+
83+
84+
85+
# Enable Swift support in CMake, force Whole Module builds (required by Embedded Swift), and use "CMAKE_Swift_COMPILER_WORKS" to
86+
# skip the trial compilations which don't (yet) correctly work when cross-compiling.
87+
set(CMAKE_Swift_COMPILER_WORKS YES)
88+
set(CMAKE_Swift_COMPILATION_MODE_DEFAULT wholemodule)
89+
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
90+
enable_language(Swift)
91+
92+
# List of Swift source files to build.
93+
target_sources(${COMPONENT_LIB}
94+
PRIVATE
95+
Main.swift
96+
Uart.swift
97+
SwiftStubs.c
98+
)

esp32-uart-echo/main/Main.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
// The code will echo the input from the UART back to the sender.
13+
@_cdecl("app_main")
14+
func main() {
15+
print("Hello from Swift on ESP32-C6!") // This will be printed to the console
16+
17+
// Initialize UART1 with TX on GPIO20 and RX on GPIO19
18+
let uart = Uart(portNum: 1, txPin: 20, rxPin: 19)
19+
20+
uart.redirectStdio()
21+
22+
while let line = readLine(strippingNewline: false) {
23+
print(line, terminator: "")
24+
}
25+
}

esp32-uart-echo/main/SwiftStubs.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <stdio.h>
2+
#include <stdint.h>
3+
#include <stdbool.h>
4+
#include <string.h>
5+
6+
// --- Stub implementations for missing Swift stdlib Unicode functions ---
7+
// These are required when using String operations in Embedded Swift
8+
bool _swift_stdlib_isExtendedPictographic(uint32_t scalar) {
9+
return false;
10+
}
11+
12+
bool _swift_stdlib_isInCB_Consonant(uint32_t scalar) {
13+
return false;
14+
}
15+
16+
uint8_t _swift_stdlib_getGraphemeBreakProperty(uint32_t scalar) {
17+
return 0;
18+
}
19+
20+
uint16_t _swift_stdlib_getNormData(uint32_t scalar) {
21+
return 0;
22+
}
23+
24+
uint32_t _swift_stdlib_getComposition(uint32_t first, uint32_t second) {
25+
return 0xFFFFFFFF; // No composition
26+
}
27+
28+
const uint8_t* _swift_stdlib_nfd_decompositions = NULL;
29+
30+
uint32_t _swift_stdlib_getDecompositionEntry(uint32_t scalar) {
31+
return 0;
32+
}
33+
34+
// --- readLine() hook ---
35+
// Swift calls swift_stdlib_readLine_stdin(&utf8Start) and frees with _swift_stdlib_free.
36+
// So we malloc() and return count.
37+
int swift_stdlib_readLine_stdin(uint8_t **outBuf) {
38+
if (!outBuf) {
39+
return -1;
40+
}
41+
42+
// Read from stdin
43+
char temp[256];
44+
if (fgets(temp, sizeof(temp), stdin) == NULL) {
45+
// EOF or error
46+
return 0;
47+
}
48+
49+
// Copy data to the output buffer
50+
size_t len = strlen(temp);
51+
*outBuf = malloc(len);
52+
if (!*outBuf) {
53+
return -1;
54+
}
55+
memcpy(*outBuf, temp, len);
56+
return (int)len;
57+
}

0 commit comments

Comments
 (0)