news 2026/4/25 15:50:39

告别裸奔通信!给你的单片机项目嵌入一个轻量级RPC框架(附nRF52/STM32源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
告别裸奔通信!给你的单片机项目嵌入一个轻量级RPC框架(附nRF52/STM32源码)

单片机跨核通信革命:轻量级RPC框架设计与实战

引言

在嵌入式系统开发中,多单片机协同工作已成为常态。传统通信方式如裸串口协议、自定义二进制格式等,往往需要开发者手动处理数据打包、校验、解析等繁琐细节。这种"裸奔式"通信不仅效率低下,还会导致代码耦合度高、维护困难。RPC(远程过程调用)框架的引入,能让开发者像调用本地函数一样操作远程资源,极大提升开发效率和代码可维护性。

本文将带你从零构建一个专为单片机设计的轻量级RPC框架,重点解决三个核心问题:

  1. 协议抽象:如何将底层通信细节与业务逻辑解耦
  2. 平台适配:如何设计跨MCU架构的通用接口
  3. 性能优化:在资源受限环境下保证通信效率

1. RPC框架核心架构设计

1.1 协议栈分层模型

一个完整的RPC框架通常采用分层设计,各层职责明确:

层级功能实现要点
应用层业务函数接口提供开发者友好的API
序列化层参数打包/解包处理字节序、对齐等问题
传输层可靠数据传输错误检测、重传机制
物理层硬件接口驱动UART/SPI/I2C等初始化

提示:在资源受限的单片机上,可以适当合并序列化层和传输层以减少开销

1.2 函数注册机制

框架需要维护一个函数注册表,核心数据结构如下:

typedef struct { void *func_ptr; // 函数指针 uint8_t param_count; // 参数个数 uint8_t param_sizes[MAX_PARAMS]; // 各参数大小 uint8_t return_size; // 返回值大小 const char *desc; // 函数描述 } RPCFunctionDef; #define MAX_FUNCTIONS 32 static RPCFunctionDef function_table[MAX_FUNCTIONS];

注册接口示例:

int rpc_register_function(uint8_t id, RPCFunctionDef *def) { if(id >= MAX_FUNCTIONS) return -1; memcpy(&function_table[id], def, sizeof(RPCFunctionDef)); return 0; }

1.3 通信协议设计

高效的协议帧格式对性能至关重要,推荐采用TLV(Type-Length-Value)结构:

+--------+--------+--------+--------+--------+ | 帧头(1B)| 函数ID(1B) | 参数长度(1B) | 参数数据(NB) | 校验和(1B) | +--------+--------+--------+--------+--------+

关键设计考量:

  • 固定长度帧头(如0xAA)便于帧同步
  • 函数ID作为路由标识
  • 参数长度字段支持变长参数
  • 简单的校验和保证数据完整性

2. 跨平台适配实现

2.1 硬件抽象层(HAL)

为支持不同MCU平台,需要抽象底层通信接口:

// hal_uart.h typedef struct { int (*init)(uint32_t baudrate); int (*send)(const uint8_t *data, uint16_t len); int (*recv)(uint8_t *buf, uint16_t len, uint32_t timeout); } UART_Driver; // 在STM32上的实现 #include "stm32f4xx_hal.h" static UART_Driver stm32_uart = { .init = HAL_UART_Init, .send = HAL_UART_Transmit, .recv = HAL_UART_Receive };

2.2 字节序处理

跨架构通信必须处理大小端问题,提供转换函数:

uint32_t rpc_htonl(uint32_t hostlong) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return __REV(hostlong); #else return hostlong; #endif }

2.3 内存管理策略

在无动态内存的系统中,可采用静态内存池:

#define POOL_SIZE 256 static uint8_t memory_pool[POOL_SIZE]; static size_t pool_index = 0; void* rpc_malloc(size_t size) { if(pool_index + size > POOL_SIZE) return NULL; void *ptr = &memory_pool[pool_index]; pool_index += size; return ptr; } void rpc_free(void) { pool_index = 0; // 简单重置 }

3. 性能优化技巧

3.1 零拷贝设计

避免不必要的数据拷贝,直接操作接收缓冲区:

int rpc_process_frame(uint8_t *frame) { uint8_t func_id = frame[1]; if(func_id >= MAX_FUNCTIONS) return -1; RPCFunctionDef *def = &function_table[func_id]; // 直接使用frame中的数据作为参数 // ... }

3.2 批量传输优化

对于大数据量传输,采用分块机制:

  1. 发送方将数据分块并编号
  2. 接收方确认收到的每个块
  3. 出错时仅重传失败块

3.3 异步调用模式

主控MCU不必阻塞等待响应:

typedef struct { uint8_t func_id; uint32_t call_id; void *params; RPC_Callback callback; } AsyncCall; void rpc_async_call(AsyncCall *call) { // 发送请求后立即返回 // 收到响应后调用callback }

4. 实战案例:nRF52与STM32通信

4.1 环境搭建

硬件连接:

  • nRF52作为从机(传感器数据采集)
  • STM32作为主机(用户界面控制)
  • 通过UART连接,波特率115200

软件依赖:

  • nRF5 SDK 17.0
  • STM32Cube HAL 1.8

4.2 从机端实现

注册传感器读取函数:

int sensor_read(float *temp, float *humi) { // 实际传感器读取代码 *temp = read_temperature(); *humi = read_humidity(); return 0; } void rpc_init() { RPCFunctionDef def = { .func_ptr = sensor_read, .param_count = 2, .param_sizes = {sizeof(float), sizeof(float)}, .return_size = sizeof(int), .desc = "Read sensor data" }; rpc_register_function(0x01, &def); }

4.3 主机端调用

封装用户友好的API:

int read_sensor_data(float *temperature, float *humidity) { uint8_t frame[10]; frame[0] = 0xAA; // 帧头 frame[1] = 0x01; // 函数ID // 发送请求 uart_send(frame, 2); // 接收响应 uint8_t resp[10]; uart_recv(resp, sizeof(resp), 100); // 解析数据 memcpy(temperature, &resp[2], sizeof(float)); memcpy(humidity, &resp[6], sizeof(float)); return resp[1]; // 状态码 }

4.4 调试技巧

常见问题排查方法:

  1. 通信失败:检查波特率、引脚连接
  2. 数据错误:验证字节序处理
  3. 性能瓶颈:使用逻辑分析仪抓取波形

注意:在nRF52上调试时,确保正确配置了低功耗模式下的UART唤醒功能

5. 框架扩展与进阶

5.1 多通信接口支持

通过适配器模式支持SPI/I2C:

typedef struct { int (*send)(void *ctx, const uint8_t *data, uint16_t len); int (*recv)(void *ctx, uint8_t *buf, uint16_t len); } RPC_Transport; void rpc_set_transport(RPC_Transport *t) { // 设置当前使用的传输接口 }

5.2 安全增强

添加简单的认证机制:

  1. 每个帧包含2字节随机数
  2. 使用预共享密钥计算HMAC
  3. 接收方验证HMAC有效性

5.3 动态函数加载

在支持Flash写入的MCU上,可实现远程更新:

int rpc_update_firmware(const uint8_t *bin, size_t len) { flash_erase(APP_ADDR); flash_write(APP_ADDR, bin, len); NVIC_SystemReset(); }

结语

在实际项目中引入RPC框架后,我们发现模块间通信代码量减少了约70%,调试效率提升明显。特别是在需要频繁添加新功能的场景下,只需在从机注册新函数,主机端就能立即调用,大幅缩短了开发周期。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 15:47:38

用 Excel 手动实现 LSTM 计算过程

前言 在学习循环神经网络时,很多人会直接使用 Python、TensorFlow 或 PyTorch 来搭建模型。这样虽然效率较高,但也容易出现一个问题:知道怎么调用模型,却不清楚模型内部到底是如何一步一步计算的。 为了更直观地理解长短期记忆网络…

作者头像 李华
网站建设 2026/4/25 15:47:35

布隆过滤器(BloomFilter)

布隆过滤器是什么? 布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用来判断:一个元素一定不存在/可能存在,它不能100%确定元素存在,但可以100%确定元素不存在。 核心原理 初始…

作者头像 李华
网站建设 2026/4/25 15:45:11

OpenRGB:3步终结RGB软件混乱,跨平台统一灯光控制终极方案

OpenRGB:3步终结RGB软件混乱,跨平台统一灯光控制终极方案 【免费下载链接】OpenRGB Open source RGB lighting control that doesnt depend on manufacturer software. Supports Windows, Linux, MacOS. Mirror of https://gitlab.com/CalcProgrammer1/O…

作者头像 李华
网站建设 2026/4/25 15:44:33

苹果触控板Windows驱动终极指南:解锁原生级精准触控体验

苹果触控板Windows驱动终极指南:解锁原生级精准触控体验 【免费下载链接】mac-precision-touchpad Windows Precision Touchpad Driver Implementation for Apple MacBook / Magic Trackpad 项目地址: https://gitcode.com/gh_mirrors/ma/mac-precision-touchpad …

作者头像 李华