Skip to content

Commit 0a24323

Browse files
authored
Merge pull request #537 from espressif/article/errors_and_logging
Added errors in esp-idf article
2 parents 4d2766f + 5bbc742 commit 0a24323

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed
36.1 KB
Loading
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
---
2+
title: "ESP-IDF tutorial series: Errors"
3+
date: "2025-08-19"
4+
showAuthor: false
5+
summary: "This article explains error handling in FreeRTOS-based embedded systems, highlighting common C practices and their limitations. It introduces ESP-IDF’s `esp_err_t` type and error-checking macros, demonstrating how they help manage errors systematically. It shows practical ways to implement error handling in embedded applications."
6+
authors:
7+
- "francesco-bez"
8+
tags: ["ESP32C3", "errors"]
9+
---
10+
11+
## Introduction
12+
13+
In microcontroller-based systems running FreeRTOS, developers work within environments that are both constrained and capable. These platforms provide low-level hardware control alongside the benefits of a lightweight real-time operating system.
14+
15+
C continues to be the primary language in this domain, valued for its efficiency, portability, and direct access to hardware. It enables precise control over performance and resource usage, which is an essential requirement for real-time applications.
16+
17+
However, C also has notable shortcomings. It lacks built-in support for structured **error handling** and **object-oriented programming (OOP)**, features that can help manage complexity and improve maintainability in large or safety-critical systems. As embedded software becomes more sophisticated, often involving numerous modules, tasks, and hardware interfaces, developers must adopt robust patterns to handle errors and organize code effectively. These needs are especially critical in FreeRTOS-based development, where tasks may fail independently and resources must be carefully managed.
18+
19+
In this article, we will examine how ESP-IDF handles error management within its codebase and how you can leverage these tools in your code.
20+
21+
## Error handling in C
22+
23+
C provides no built-in exception handling, so error management is a manual, discipline-driven part of embedded development. In systems using FreeRTOS, where multiple tasks may run concurrently and shared resources must be protected, robust error handling becomes even more important to ensure system stability and predictable behavior.
24+
25+
26+
### Common Techniques
27+
28+
Over the years, two common techniques for managing errors have emerged to manage errors.
29+
30+
1. __Return Codes__<br>
31+
The most widespread method is returning status codes from functions to indicate success or failure. These codes are often:
32+
33+
* Integers (`0` for success, non-zero for errors or warnings)
34+
* Enumerations representing different error types
35+
* `NULL` pointers for memory allocation or resource acquisition failures
36+
37+
Each calling function is responsible for checking the return value and taking appropriate action, such as retrying, logging, or aborting.
38+
39+
2. __Global `errno` Variable__<br>
40+
The C standard library defines a global variable `errno` to indicate error conditions set by certain library or system calls (e.g., `fopen`, `malloc`). After a function call, a developer can check `errno` to understand what went wrong. It’s typically used like this:
41+
42+
```c
43+
FILE *fp = fopen("config.txt", "r");
44+
if (fp == NULL) {
45+
printf("File open failed, errno = %d\n", errno);
46+
}
47+
```
48+
49+
However, in embedded systems FreeRTOS, `errno` comes with important caveats:
50+
51+
* It is often __shared globally__, which makes it __unsafe__ in multi-tasking environments.
52+
* Some implementations (like `newlib` with thread-safety enabled) provide __thread-local `errno`__, but this increases memory usage.
53+
* It is rarely used in embedded systems due to its "implicit" nature.
54+
55+
In FreeRTOS-based applications, the use of return code is typically followed approach.
56+
57+
In embedded system design, development frameworks often also define:
58+
59+
1. __Custom error types__<br>
60+
Many embedded projects define their own error handling systems, which typically include a consistent error type definitions across modules (e.g., `typedef int err_t;`)
61+
62+
2. __Macros for error checking__<br>
63+
To reduce repetitive boilerplate code, macros are often used to check errors and handle cleanup in a consistent way:
64+
65+
```c
66+
#define CHECK(expr) do { if (!(expr)) return ERR_FAIL; } while (0)
67+
```
68+
69+
These can help standardize behavior across tasks and improve code readability.
70+
71+
In conclusion, in RTOS-based embedded systems where robustness and reliability are critical, manual error handling must be systematic and consistent. While `errno` exists and can be used cautiously, most embedded applications benefit more from explicit well-defined error enums and structured reporting mechanisms.
72+
73+
### ESP-IDF approach
74+
75+
ESP-IDF defines its error codes as `esp_err_t` type and provides a couple of error checking macros.
76+
77+
* __`esp_err_t` -- Structured error codes__<br>
78+
Espressif’s ESP-IDF framework introduces a standardized error handling approach through the use of the `esp_err_t` type. This is a 32-bit integer used to represent both generic and module-specific error codes. The framework defines a wide range of constants, such as:
79+
80+
```c
81+
#define ESP_OK 0 // Success
82+
#define ESP_FAIL 0x101 // Generic failure
83+
#define ESP_ERR_NO_MEM 0x103 // Out of memory
84+
#define ESP_ERR_INVALID_ARG 0x102 // Invalid argument
85+
```
86+
The full list is available on the [error codes documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/error-codes.html) page.
87+
88+
Developers typically write functions that return `esp_err_t`, and use these codes to control program flow or report diagnostics. `ESP_OK` is zero and errors can be easily checked like this:
89+
90+
```c
91+
esp_err_t ret = i2c_driver_install(...);
92+
if (ret != ESP_OK) {
93+
printf("I2C driver install failed: %s", esp_err_to_name(ret));
94+
return ret;
95+
}
96+
```
97+
98+
ESP-IDF also provides utilities like `esp_err_to_name()` and `esp_err_to_name_r()` to convert error codes into readable strings for logging, which is particularly helpful for debugging.
99+
100+
* __`ESP_ERROR_CHECK` -- Error checking macro__<br>
101+
To reduce repetitive error checking code in testing and examples, ESP-IDF includes macros like `ESP_ERROR_CHECK()`. This macro evaluates an expression (typically a function call returning `esp_err_t`), logs a detailed error message if the result is not `ESP_OK`, and aborts the program. You will find this macro repeatedly used in ESP-IDF examples:
102+
103+
```c
104+
ESP_ERROR_CHECK(i2c_driver_install(...));
105+
```
106+
There is also `ESP_ERROR_CHECK_WITHOUT_ABORT` version of this macro which doesn't stop execution if an error is returned.
107+
108+
Out of curiosity, let's look at the macro definition
109+
```c
110+
#define ESP_ERROR_CHECK(x) do { \
111+
esp_err_t err_rc_ = (x); \
112+
if (unlikely(err_rc_ != ESP_OK)) { \
113+
abort(); \
114+
} \
115+
} while(0)
116+
```
117+
118+
At its core, this macro simply checks whether the given value equals `ESP_OK` and halts execution if it does not. However, there are two elements that might seem confusing if you're not familiar with C macros:
119+
120+
1. **The `do { } while (0)` wrapper**<br>
121+
This is a common best practice when writing multi-line macros. It ensures the macro behaves like a single statement in all contexts, helping avoid unexpected behavior during compilation. If you're curious about the reasoning behind this pattern, [this article](https://vcstutoring.ca/wrapping-multiline-macros-in-c-with-do-while/) offers a good explanation.
122+
123+
2. **The `unlikely` function**<br>
124+
This is used as a compiler optimization hint. It tells the compiler that the condition `err_rc_ != ESP_OK` is expected to be false most of the time—i.e., errors are rare—so it can optimize for the more common case where `ESP_OK` is returned. While it doesn't change the program's behavior, it can improve performance by guiding branch prediction and code layout.
125+
126+
127+
## Examples
128+
129+
In this section, we’ll start with a simple, self-contained example to demonstrate how error codes and macros work in practice. Then, we’ll examine how these concepts are applied in an actual ESP-IDF example by reviewing its source code.
130+
131+
### Basic Example: Division
132+
133+
To demonstrate the use of the error codes, we will implement a division function.
134+
135+
<!-- #### Create a new project
136+
137+
First, we need to create a new project using the `template-app` template. In VS Code, this can be done through the command palette (indicated here as `>`):
138+
139+
* `> ESP-IDF: Create Project from Extension Template`
140+
&rarr; Select a container directory
141+
&rarr; Choose `template-app` -->
142+
143+
<!--
144+
#### Implementation -->
145+
146+
Unlike other basic operations, division can result in an error if the second argument is zero, which is not allowed. To handle this case, we use an `esp_err_t` return type for error reporting.
147+
148+
<!-- Add the following code to your `main.c` file: -->
149+
The division function is implemented as follows:
150+
151+
```c
152+
esp_err_t division(float * result, float a, float b){
153+
154+
if(b==0 || result == NULL){
155+
return ESP_ERR_INVALID_ARG;
156+
}
157+
158+
*result = a/b;
159+
return ESP_OK;
160+
}
161+
```
162+
163+
As you can see, since the function’s return type is `esp_err_t`, we need an alternative way to return the division result. The standard approach is to pass a pointer to a result variable as an argument. While this may seem cumbersome for a simple application, its advantages become increasingly clear when applying object-oriented programming (OOP) principles in C.
164+
165+
In the `app_main` function, we first check for errors before printing the result.
166+
167+
```c
168+
void app_main(void)
169+
{
170+
printf("\n\n*** Testing Errors ***!\n\n");
171+
float division_result = 0;
172+
float a = 10.5;
173+
float b = 3.3;
174+
175+
if(division(&division_result,a,b)==ESP_OK){
176+
printf("Working division: %f\n", division_result);
177+
}else{
178+
printf("Division Error!\n");
179+
}
180+
181+
b = 0;
182+
183+
if(division(&division_result,a,b)==ESP_OK){
184+
printf("Working division: %f\n", division_result);
185+
}else{
186+
printf("Division Error!\n");
187+
}
188+
189+
}
190+
```
191+
192+
And the result is as expected
193+
194+
```bash
195+
*** Testing Errors ***!
196+
197+
Working division: 3.181818
198+
Division Error!
199+
```
200+
201+
We can also use `ESP_ERROR_CHECK_WITHOUT_ABORT(division(&division_result,a,b))` instead of the if/else block.
202+
It results is a silent pass for the first function call and in the following message for the second.
203+
204+
```bash
205+
ESP_ERROR_CHECK_WITHOUT_ABORT failed: esp_err_t 0x102 (ESP_ERR_INVALID_ARG) at 0x4200995a
206+
--- 0x4200995a: app_main at <folder_path>/error-example/main/main.c:37
207+
208+
file: "./main/main.c" line 37
209+
func: app_main
210+
expression: division(&division_result, a, b)
211+
```
212+
213+
Using `ESP_ERROR_CHECK` makes the system reboot after the error is found.
214+
215+
216+
### ESP-IDF example
217+
218+
Let’s examine a more complete example taken directly from the ESP-IDF example folder. The following code is from the [HTTPS request example](https://github.com/espressif/esp-idf/blob/v5.4.2/examples/protocols/https_request/main/https_request_example_main.c).
219+
220+
The `app_main` function code is as follows
221+
```c
222+
void app_main(void)
223+
{
224+
ESP_ERROR_CHECK(nvs_flash_init());
225+
ESP_ERROR_CHECK(esp_netif_init());
226+
ESP_ERROR_CHECK(esp_event_loop_create_default());
227+
//[...]
228+
ESP_ERROR_CHECK(example_connect());
229+
230+
if (esp_reset_reason() == ESP_RST_POWERON) {
231+
ESP_ERROR_CHECK(update_time_from_nvs());
232+
}
233+
234+
const esp_timer_create_args_t nvs_update_timer_args = {
235+
.callback = (void *)&fetch_and_store_time_in_nvs,
236+
};
237+
238+
esp_timer_handle_t nvs_update_timer;
239+
ESP_ERROR_CHECK(esp_timer_create(&nvs_update_timer_args, &nvs_update_timer));
240+
ESP_ERROR_CHECK(esp_timer_start_periodic(nvs_update_timer, TIME_PERIOD));
241+
242+
xTaskCreate(&https_request_task, "https_get_task", 8192, NULL, 5, NULL);
243+
}
244+
```
245+
246+
As you can see, almost all function calls are surrounded by the `ESP_ERROR_CHECK` macro.
247+
248+
{{< alert iconColor="#df8e1d" cardColor="#edcea3">}}
249+
`ESP_ERROR_CHECK` is used only in examples and prototypes because it aborts execution on error. It should not be used in production code, where a properly designed error-handling mechanism is preferred.
250+
{{< /alert >}}
251+
252+
253+
## Conclusion
254+
255+
In this article, we examined error handling in FreeRTOS-based embedded systems, focusing on the ESP-IDF framework. We covered common C techniques, the importance of systematic error management, and how ESP-IDF uses `esp_err_t` and macros to simplify error checking. Through both a simple example and a real-world ESP-IDF example, we saw practical applications of these concepts to improve code robustness and reliability.

0 commit comments

Comments
 (0)