In embedded development, when a system needs to communicate across chips or cores, traditional methods often require handling complex low-level communication details. Today, we will introduce a lightweight RPC framework designed specifically for embedded environments—eRPC—and see how it simplifies this process.
1. What is eRPC?
eRPC (Embedded Remote Procedure Call) is an open-source remote procedure call system designed for multi-chip embedded systems and heterogeneous multi-core SoCs. Unlike other modern RPC systems (such as Apache Thrift), eRPC’s uniqueness lies in its lightweight design (code size < 5kB) and optimization for tightly coupled systems. It allows developers to export existing functions directly using pure C without modifying function prototypes.Core features of eRPC
Ultra-lightweight: Compiled code size is less than 5kB, suitable for resource-constrained embedded environments
Cross-platform support: Supports various transport methods (UART, SPI, TCP/IP, etc.)Code generation: Provides the erpcgen tool to automatically generate client/server code from IDL filesTransparent communication: Makes remote calls as simple as local function calls
2. Setting Up the eRPC Environment and Project Structure
Environment Setup
First, we need to obtain the eRPC source code and compile it:
# Clone the eRPC repository
git clone https://github.com/EmbeddedRPC/erpc.git
cd erpc
# Compile the eRPC library and code generation tool (refer to erpc's README)
make erpc erpcgen
3. Detailed Explanation of .erpc Files: Interface Definition Language
eRPC uses an Interface Definition Language (IDL) to define remote interfaces and data types, which are stored in .erpc files.
Basic Syntax Structure
A complete .erpc file contains the following parts:
// led.erpc - LED control interface definition
/* Enumeration type definition */
enum LEDName { kRed = 1, kGreen = 2, kBlue = 3}
/* Structure definition */
struct LEDStatus { bool is_on; int intensity;}
/* Interface definition */
interface IO { // Control LED switch set_led(LEDName which_led, bool on_or_off) -> void
// Get LED status get_led_status(LEDName which_led) -> LEDStatus
// Function with complex parameters set_led_intensity(LEDName which_led, int intensity) -> bool}
Supported Data Types
eRPC supports a rich set of data types, including:Basic types:bool, int8, int16, int32, float, doubleComplex types: struct, enum, binary (binary data)Container types: list, map
4. Code Generation Practice
After defining the .erpc file, use the erpcgen tool to generate code:
# Generate C language code
erpcgen -l c led.erpc
# Generate Python code erpcgen -l python led.erpc
After execution, the following files will be generated:led_client.cpp: Client code<span>led_server.cpp</span>: Server code<span>led.h</span>: Common header file<span>led_server.h</span>: Server header file
5. Complete Example: LED Control System
Below is a complete LED control example demonstrating the practical application of eRPC.
1. Client Implementation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <erpc_client_setup.h>
#include <erpc_port.h>
#include "led.h" // Generated header file
int main(int argc, char *argv[]){ // 1. Initialize transport layer (using TCP here) erpc_transport_t transport = erpc_transport_tcp_init("127.0.0.1", 5555, false);
// 2. Initialize message buffer factory erpc_mbf_t message_buffer_factory = erpc_mbf_dynamic_init();
// 3. Initialize eRPC client erpc_client_init(transport, message_buffer_factory);
printf("eRPC LED client has started\n");
// 4. Call remote function - turn on red LED printf("Turning on red LED...\n"); set_led(kRed, true);
// 5. Call remote function - get LED status LEDStatus status = get_led_status(kRed); printf("Red LED status: %s, Intensity: %d\n", status.is_on ? "On" : "Off", status.intensity);
// 6. Clean up resources erpc_transport_tcp_close();
return 0;}
2. Server Implementation
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <erpc_server_setup.h>
#include "led_server.h" // Generated server header file
// Global LED status array
LEDStatus g_led_status[3] = { {false, 0}, // kRed {false, 0}, // kGreen {false, 0} // kBlue};
// Implement set_led function
void set_led(LEDName which_led, bool on_or_off){ if (which_led < kRed || which_led > kBlue) { printf("Error: Invalid LED number %d\n", which_led); return; }
int led_index = which_led - 1; g_led_status[led_index].is_on = on_or_off;
printf("LED %d status set to: %s\n", which_led, on_or_off ? "On" : "Off");}
// Implement get_led_status function
LEDStatus get_led_status(LEDName which_led){ LEDStatus status = {false, 0};
if (which_led >= kRed && which_led <= kBlue) { int led_index = which_led - 1; status = g_led_status[led_index]; }
printf("Querying LED %d status: %s, Intensity: %d\n", which_led, status.is_on ? "On" : "Off", status.intensity);
return status;}
// Implement set_led_intensity function
bool set_led_intensity(LEDName which_led, int intensity){ if (which_led < kRed || which_led > kBlue) { printf("Error: Invalid LED number %d\n", which_led); return false; }
if (intensity < 0 || intensity > 100) { printf("Error: Intensity value %d out of range (0-100)\n", intensity); return false; }
int led_index = which_led - 1; g_led_status[led_index].intensity = intensity;
printf("LED %d intensity set to: %d\n", which_led, intensity); return true;}
int main(int argc, char *argv[]){ // 1. Initialize TCP transport layer (server mode) erpc_transport_t transport = erpc_transport_tcp_init("127.0.0.1", 5555, true);
// 2. Initialize message buffer factory erpc_mbf_t message_buffer_factory = erpc_mbf_dynamic_init();
// 3. Initialize eRPC server erpc_server_t server = erpc_server_init(transport, message_buffer_factory);
// 4. Add LED service erpc_add_service_to_server(server, create_IO_service());
printf("eRPC LED server has started, listening on port 5555...\n");
// 5. Run server (infinite loop) while (1) { erpc_server_run(server); }
// 6. Clean up resources (usually won't reach here) erpc_transport_tcp_close();
return 0;}
6. Advanced Features and Best Practices
1. Multi-Transport Layer Support
eRPC supports multiple transport methods, simply modify the initialization code:
// UART transport (commonly used in embedded systems)
erpc_transport_t transport = erpc_transport_cmsis_uart_init(Driver_USART0);
// SPI transport
erpc_transport_t transport = erpc_transport_dspi_init(spi_master_instance);
// TCP/IP transport
erpc_transport_t transport = erpc_transport_tcp_init("192.168.1.100", 8080, false);
2. Error Handling Mechanism
eRPC provides a comprehensive error handling mechanism:
// Define error callback function
void erpc_error_handler(erpc_status_t err, const char* function_name) { printf("eRPC Error: Function %s returned status %d\n", function_name, err);}
// Register error handler
erpc_set_error_handler(erpc_error_handler);
3. Memory Management Best Practices
In resource-constrained embedded environments, managing memory properly is crucial:
// Use static memory allocation to avoid fragmentation
#define MAX_BUFFER_SIZE 1024
static uint8_t s_buffer_pool[MAX_BUFFER_SIZE];
// Initialize static message buffer factory
erpc_mbf_t message_buffer_factory = erpc_mbf_static_init(s_buffer_pool, MAX_BUFFER_SIZE);
// Release binary data promptly after use
binary_t* receive_binary(binary_t* input) { binary_t* result = (binary_t*)erpc_malloc(sizeof(binary_t)); result->data = (uint8_t*)erpc_malloc(input->dataLength); result->dataLength = input->dataLength;
// Process data...
return result;}
7. Practical Application Scenarios
1. Multi-Core SoC Communication
In heterogeneous multi-core processors, eRPC can simplify inter-core communication:
// Core A (Cortex-A series) as server providing advanced features
// Core B (Cortex-M series) as client calling these features
// Core B client code
void enable_advanced_feature() { // Call complex algorithm provided by Core A advanced_result_t result = compute_on_cortex_a(complex_data);
// Process result...
}
2. Smart Home Systems
In smart homes, multiple microcontrollers can communicate via eRPC:
// Main controller (server)
interface SmartHome { set_light_intensity(room_id, intensity) -> bool get_temperature(sensor_id) -> float set_thermostat_mode(mode) -> bool}
// Room controller (client) calls main controller functions
8. Considerations and Limitations
Although eRPC is powerful, there are several points to consider when using it:Security: The eRPC transport layer lacks built-in encryption and authentication mechanisms, transmitting plaintext data, so a security layer needs to be added in high-security scenarios.Performance Limitations: eRPC is designed for tightly coupled embedded systems and is not suitable for high-performance distributed systems.Error Handling: Careful handling of network exceptions and timeouts is required to ensure system stability.Memory Management: In extremely resource-constrained environments, careful management of memory allocation and release is necessary.
Conclusion
As a lightweight embedded RPC framework, eRPC greatly simplifies the complexity of communication between embedded systems. By defining interfaces in .erpc files, the erpcgen tool automatically generates communication code, allowing developers to focus on business logic rather than low-level communication details.This article provides a detailed introduction to the basic concepts of eRPC, environment setup, .erpc file syntax, complete example code, and best practices. We hope this content helps you efficiently implement inter-module communication in your next embedded project.
Project address: https://github.com/EmbeddedRPC/erpc