Skip to content

Commit 183cc2f

Browse files
committed
Added porting library as esp-idf component article, moved folder, implemented review
1 parent 9886e89 commit 183cc2f

File tree

3 files changed

+289
-0
lines changed

3 files changed

+289
-0
lines changed
32.4 KB
Loading
18 KB
Loading
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
---
2+
title: "Porting a library to an ESP-IDF component"
3+
date: "2025-10-20"
4+
summary: "This article shows how to port an external library into an ESP-IDF project by converting it into a reusable component. Using tinyexpr as an example, it covers obtaining the source code, creating a new project, building a component, configuring the build system, and testing on hardware."
5+
authors:
6+
- "francesco-bez"
7+
tags: ["esp32c3", "component","porting"]
8+
---
9+
10+
## Introduction
11+
12+
When developing with [ESP-IDF](https://github.com/espressif/esp-idf), you may eventually need functionality that isn’t provided by its built-in components. In many cases, the solution is an open-source library from GitHub or another repository. However, these external libraries are not always structured for ESP-IDF, and simply copying their source files into your project can lead to messy integrations, difficult updates, and code that’s hard to reuse or share.
13+
14+
A cleaner and more maintainable approach is to wrap the external library into a **reusable ESP-IDF component**. Components integrate naturally with the ESP-IDF build system, can be reused across multiple projects, and make sharing and maintaining code much easier. They also help keep your project organized and scalable over time.
15+
16+
In this article, we’ll demonstrate this approach by porting the [tinyexpr](https://github.com/codeplea/tinyexpr?tab=readme-ov-file#tinyexpr) library into a fully functional ESP-IDF component, ready for reuse in future applications.
17+
To keep things simple, we’ll start by manually adding the library’s source and header files into a new component structure. In a future article, we’ll extend this setup by adding the library as a git submodule, making it easier to stay synchronized with the upstream repository as new features or bug fixes are released.
18+
19+
We’ll be using an **ESP32-C3-DevKitM-1** board and the **ESP-IDF Extension for VS Code**, though the same steps apply to other boards and SoCs.
20+
21+
## Prepare to create a component
22+
23+
To keep things straightforward, we will work with a self-contained library that does not rely on specific peripherals. A good example of such a library is _tinyexpr_, which we will use throughout this article.
24+
25+
Integrating an external library as an ESP-IDF component involves several steps:
26+
27+
1. Obtain library code
28+
2. Set up a test project in ESP-IDF
29+
3. Test the component
30+
4. Solve compatibility issues
31+
32+
These are the steps we will follow in the rest of the article.
33+
34+
## Obtain library code
35+
36+
The tinyexpr code is available on GitHub:
37+
38+
{{< github repo="codeplea/tinyexpr" >}}
39+
40+
We can clone the repository to inspect the files:
41+
42+
```bash
43+
git clone https://github.com/codeplea/tinyexpr.git
44+
```
45+
46+
Although the repository contains several source files, the library itself consists of just two key files, as noted in the `README`:
47+
48+
* `tinyexpr.h`
49+
* `tinyexpr.c`
50+
51+
There are also several examples in the repo, showing how to use the library. The simplest one is `example.c`, so it's a good idea to use it to test our component.
52+
53+
## Set up a test project in ESP-IDF
54+
55+
To work on the porting, we need to
56+
1. Create a new project
57+
2. Create a new component
58+
59+
### Create a new project
60+
61+
To keep things simple and organized, we will start with a basic project using a template app. You can follow one of the two approaches below:
62+
63+
{{< tabs groupId="devtool" >}}
64+
{{% tab name="ESP-IDF Extension for VS Code New Project" %}}
65+
66+
* In VS Code command palette enter: `> ESP-IDF: New Project`
67+
68+
You will be presented with a screen like Fig.1.
69+
{{< figure
70+
default=true
71+
src="img/new_project.webp"
72+
height=500
73+
caption="Fig.1 - Choosing the project settings"
74+
>}}
75+
76+
If you're using the ESP32-C3-DevKitM, fill the fields as follows:
77+
* Project name: `esp_tinyexpr_test`
78+
* Choose ESP-IDF Target: `esp32c3`
79+
* Choose ESP-IDF Board: `ESP32-C3 chip (via builtin USB-JTAG)`
80+
* Choose serial port: <YOUR SERIAL PORT> (e.g. `COM25` or `/dev/tty.usbserial-11133`)
81+
82+
Feel free to change the settings according to your platform.
83+
84+
* Click on `Choose Template` and select `template_app`
85+
* Open the project with VS Code
86+
87+
You should now have the following project structure:
88+
```bash
89+
.
90+
├── CMakeLists.txt
91+
├── main
92+
│   ├── CMakeLists.txt
93+
│   └── main.c
94+
└── README.md
95+
```
96+
97+
98+
{{% /tab %}}
99+
{{% tab name="Clone basic example" %}}
100+
* Download the basic example code found on [this github repo](https://github.com/FBEZ-docs-and-templates/devrel-tutorials-code/tree/main/tutorial-basic-project).
101+
* Open the project with VSCode
102+
* `> ESP-IDF: Set Espressif Device target`
103+
* `> ESP-IDF: Select Port to Use`
104+
105+
{{% /tab %}}
106+
{{< /tabs >}}
107+
108+
109+
### Create a new component
110+
111+
Next, we will create a new component named `my_tinyexpr` to hold the `tinyexpr` files.
112+
113+
* Select `> ESP-IDF: Create New ESP-IDF Component`
114+
115+
* Name it `my_tinyexpr`
116+
117+
Your project structure now looks like this.
118+
119+
```bash
120+
.
121+
├── CMakeLists.txt
122+
├── components
123+
│ └── my_tinyexpr
124+
│ ├── CMakeLists.txt
125+
│ ├── include
126+
│ │ └── my_tinyexpr.h
127+
│ └── my_tinyexpr.c
128+
├── main
129+
│ ├── CMakeLists.txt
130+
│ └── main.c
131+
├── README.md
132+
├── sdkconfig
133+
└── sdkconfig.old
134+
```
135+
136+
* Replace `my_tinyexpr.c` with the downloaded `tinyexpr.c`
137+
* Replace `my_tinyexpr.h` with the downloaded `tinyexpr.h`
138+
139+
Since the filenames differ from the default, update the component's `CMakeLists.txt` to register the correct source file:
140+
141+
```txt
142+
idf_component_register(SRCS "tinyexpr.c"
143+
INCLUDE_DIRS "include")
144+
```
145+
146+
{{< alert icon="circle-info" cardColor="#b3e0f2" iconColor="#04a5e5">}}
147+
The build system automatically includes all files in the `include` directory, so no additional configuration is needed to locate `tinyexpr.h`.
148+
{{< /alert >}}
149+
150+
Now that we’ve created the new component, it’s time to test it.
151+
152+
## Test the component
153+
154+
To test the component we need to
155+
1. Inform the build system about the new component
156+
2. Include the header file
157+
3. Call the function of the library
158+
159+
### Inform the build system about the new component
160+
161+
In the `CMakeLists.txt` of the `__main__` component, add `REQUIRES "my_tinyexpr"` to let the build system know about the new component:
162+
163+
```txt
164+
idf_component_register(SRCS "main.c"
165+
REQUIRES "my_tinyexpr"
166+
INCLUDE_DIRS ".")
167+
```
168+
169+
This ensures that the build system includes `my_tinyexpr` when compiling your project.
170+
171+
172+
### Include the header file
173+
174+
In your main file, include the header from the `tinyexpr` library:
175+
176+
```c
177+
#include "tinyexpr.h"
178+
```
179+
180+
181+
{{< alert icon="lightbulb" iconColor="#179299" cardColor="#9cccce">}}
182+
In general, a component’s header file does not need to match its name, and this applies broadly across ESP-IDF projects.
183+
{{< /alert >}}
184+
185+
### Call a function of the library
186+
187+
In the tinyexpr repository, there is an `example.c` file that demonstrates how to use the library.
188+
189+
* Copy the relevant portions of `example.c` into your `main.c` file.
190+
191+
Your `app_main` function should now look like this:
192+
193+
```c
194+
#include <stdio.h>
195+
#include "tinyexpr.h"
196+
197+
void app_main(void)
198+
{
199+
const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)";
200+
double r = te_interp(c, 0);
201+
printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r);
202+
}
203+
```
204+
205+
Next, build, flash, and monitor the project:
206+
207+
* `ESP-IDF: Build, Flash and Start a Monitor on Your Device`
208+
209+
And we got and error!
210+
211+
```console
212+
In file included from <PATH>/tutorial-porting-tinyexpr/components/my_tinyexpr/tinyexpr.c:43:
213+
<PATH>/tutorial-porting-tinyexpr/components/my_tinyexpr/tinyexpr.c: In function 'next_token':
214+
<PATH>/tutorial-porting-tinyexpr/components/my_tinyexpr/tinyexpr.c:255:32: error: array subscript has type 'char' [-Werror=char-subscripts]
215+
255 | if (isalpha(s->next[0])) {
216+
| ~~~~~~~^~~
217+
<PATH>/tutorial-porting-tinyexpr/components/my_tinyexpr/tinyexpr.c:258:39: error: array subscript has type 'char' [-Werror=char-subscripts]
218+
258 | while (isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_')) s->next++;
219+
| ~~~~~~~^~~
220+
<PATH>/tutorial-porting-tinyexpr/components/my_tinyexpr/tinyexpr.c:258:62: error: array subscript has type 'char' [-Werror=char-subscripts]
221+
258 | while (isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_')) s->next++;
222+
| ~~~~~~~^~~
223+
cc1: some warnings being treated as errors
224+
ninja: build stopped: subcommand failed.
225+
```
226+
227+
There is a compatibility issue. Although the library you found is written in C and appears to work, compiler settings or library dependencies could still cause problems. Let’s investigate what is preventing the compilation.
228+
229+
## Solve compatibility issues
230+
231+
The error is in the following code.
232+
233+
```c
234+
if (isalpha(s->next[0])) { ... }
235+
while (isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_')) s->next++;
236+
```
237+
238+
It happens because a `char` is directly passed to `isalpha()` and `isdigit()`. On many platforms, `char` is signed, and these functions expect an `int` in the range of `unsigned char` (or `EOF`). Passing a signed `char` can trigger warnings or undefined behavior.
239+
240+
ESP-IDF uses very strict compilation flags and treats all warnings as errors (`-Werror`), which is why these warnings stop the build.
241+
242+
So we have two options, changing the code or changing the compilation flag.
243+
244+
{{< alert iconColor="#df8e1d" cardColor="#edcea3">}}
245+
Modifying the code of an external library is not recommended, as it makes it harder to keep your version synchronized with the official release and to apply future updates or bug fixes.
246+
{{< /alert >}}
247+
248+
249+
### Changing the code
250+
251+
A fast way to fix it is to cast the character to `unsigned char`:
252+
253+
```c
254+
if (isalpha((unsigned char)s->next[0])) { ... }
255+
256+
while (isalpha((unsigned char)s->next[0]) ||
257+
isdigit((unsigned char)s->next[0]) ||
258+
(s->next[0] == '_')) s->next++;
259+
```
260+
261+
Now we can build, flash, and monitor the project again.
262+
263+
* `ESP-IDF: Build, Flash and Start a Monitor on Your Device`
264+
265+
And we get the expected output.
266+
267+
```console
268+
The expression:
269+
sqrt(5^2+7^2+11^2+(8-2)^2)
270+
evaluates to:
271+
15.198684
272+
```
273+
274+
### Changing the compilation flag
275+
276+
Alternatively, the issue can be resolved by adjusting the compilation flags instead of modifying the source code. Since the error originates from `-Werror=char-subscripts`, we can suppress it by adding the following line to the `CMakeLists.txt` file of your component:
277+
278+
```cmake
279+
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-char-subscripts)
280+
```
281+
282+
This approach is often preferable when working with external libraries, as it allows you to keep the original source code intact. It also makes it easier to manage updates: if the library is included as a git submodule, any future improvements or security fixes can be applied by simply updating the submodule, without the need to reapply code changes.
283+
284+
285+
Now that we’ve covered the basics of creating a component and resolving common compatibility issues, the next article will build on this foundation. You’ll see how to import a component as a git submodule, integrate it into multiple projects, and share it with the community. This workflow not only helps keep your code organized and maintainable but also ensures that updates and improvements can be easily propagated across projects without modifying the original source.
286+
287+
## Conclusion
288+
289+
In this article, we demonstrated how to take an existing open source library and integrate it into an ESP-IDF project as a reusable component. We located the tinyexpr library, created a new ESP-IDF project, built a dedicated component for the library, resolved compatibility details, and verified its functionality on an ESP32-C3-DevkitM-1 board. By packaging the library as a component rather than copying source files directly, we ensured cleaner integration, easier maintenance, and effortless reuse in future projects.

0 commit comments

Comments
 (0)