
来源:https://gitee.com/MacRsh/mr-library
MR-library(简称MR框架)是一款专为资源受限的嵌入式系统(尤其是MCU)设计的轻量级设备框架。它充分考虑了嵌入式开发在资源与性能上的特殊需求,通过提供一套标准化的设备管理层,将应用程序与底层硬件驱动解耦,从而显著降低了嵌入式应用开发的门槛与复杂度,帮助开发者高效构建稳定、可移植的应用程序。
该框架为开发者抽象出统一的设备操作接口,包括开启(open)、关闭(close)、控制(ioctl)、读(read)、写(write) 等。应用程序只需调用这些标准接口访问设备,无需关心底层驱动的具体实现细节,这有助于提升代码的整洁性与可维护性。更重要的是,当硬件平台更换时,开发者仅需为新硬件适配底层驱动,应用程序代码即可实现无缝迁移,极大地提升了软件的可重用性与项目的可扩展性。

关键特性
- 标准化的设备访问接口:统一的
open/close/read/write/ioctl操作模型。
- 应用程序与驱动解耦:应用层不依赖具体驱动实现,便于移植和维护。
- 开发简化:同时简化了底层驱动开发和上层应用程序开发。
- 轻量高效:核心框架代码精简,对ROM/RAM资源占用极低。
- 模块化设计:各组件解耦,支持独立开发与功能裁剪,硬件迁移成本极低。
- 环境兼容:支持在无操作系统的裸机环境和RTOS等操作系统环境下运行。
主要组成
- 设备框架:提供标准设备访问接口。
- 内存管理:提供动态内存管理模块。
- 工具库:包含链表、队列、平衡二叉树等常用数据结构。
- 功能组件:各类可选的软件组件。
标准化设备接口
所有设备操作均通过以下核心接口完成,这种设计思想与Linux设备驱动模型有异曲同工之妙,但在资源占用上做了极致优化,更适合单片机环境。
| 接口 |
描述 |
mr_dev_register |
注册设备 |
mr_dev_open |
打开设备 |
mr_dev_close |
关闭设备 |
mr_dev_ioctl |
控制设备 |
mr_dev_read |
从设备读取数据 |
mr_dev_write |
向设备写入数据 |
使用示例:
struct mr_spi_dev spi_dev;
int main(void)
{
/* 注册SPI10设备(CS低电平有效)到SPI1总线上 */
mr_spi_dev_register(&spi_dev, "spi1/spi10", 0, MR_SPI_CS_ACTIVE_LOW);
/* 打开SPI1总线下的SPI10设备 */
int ds = mr_dev_open("spi1/spi10", MR_OFLAG_RDWR);
/* 发送数据 */
uint8_t wr_buf[] = {0x01, 0x02, 0x03, 0x04};
mr_dev_write(ds, wr_buf, sizeof(wr_buf));
/* 接收数据 */
uint8_t rd_buf[4] = {0};
mr_dev_read(ds, rd_buf, sizeof(rd_buf));
/* 关闭设备 */
mr_dev_close(ds);
}
配置工具
MR-library 集成了 Kconfig 可视化配置工具,开发者无需深入源码即可完成功能裁剪与参数配置,这对于复杂的嵌入式C语言工程项目管理尤为便捷。
Kconfig 会根据工程中的配置文件自动生成一个层级式的配置菜单界面。开发者通过方向键和回车键即可轻松选择需要启用的功能组件,并设置相关参数。


通过修改参数,可以快速裁剪出符合项目需求的轻量级库。配置完成后,运行配套的 Python 脚本即可自动生成最终的项目配置文件 mr_config.h。
目录结构
| 名称 |
描述 |
bsp |
板级支持包 |
components |
组件 |
device |
设备文件 |
document |
文档 |
driver |
驱动文件 |
include |
库头文件 |
source |
库源文件 |
Kconfig |
配置文件 |
kconfig.py |
自动配置脚本 |
LICENSE |
许可证 |
开始使用
配置 Kconfig 环境
注:Kconfig 工具并非强制使用,但极力推荐,其安装配置快捷,且能极大提升开发效率。
- 检查Python环境:在命令行中运行
python --version 检查Python版本(Kconfig 依赖 python,若未安装请先行安装)。
- 安装Kconfiglib:在命令行中执行以下命令进行安装。
python -m pip install windows-curses
python -m pip install kconfiglib
- 验证安装:在命令行中运行
menuconfig -h,若显示帮助信息则表示安装成功。
将框架导入工程
- 获取源码:从 Gitee 或 Github 仓库下载最新版本源码。
- 放置源码:将源码解压并放置到您的工程目录中。以STM32工程为例:
工程目录/
├── .mxproject
├── Core/
├── Drivers/
├── MDK-ARM/
└── mr-library/ <-- 将源码放在这里
- BSP配置:如果使用的芯片已有适配的BSP,请参考对应BSP的教程完成配置。
- 精简目录:移除工程不需要的目录,如
bsp、document、module(若无需Git管理,也可删除 .git 文件夹)。精简后目录如下:
工程目录/
├── .mxproject
├── Core/
├── Drivers/
├── MDK-ARM/
└── mr-library/
├── device/
├── driver/
├── include/
├── source/
└── ...
- 添加文件到IDE:将
source、device、driver 目录下的所有源文件添加到IDE工程中(大部分IDE可自动识别)。
配置菜单选项
- 在
mr-library 目录下打开命令行,运行 menuconfig 进入配置界面。
注:添加对应芯片的驱动后,菜单中将显示 Device configure 和 Driver configure 选项。
- 选中
Device configure 回车进入,根据需求配置功能模块。
- 配置完成后,按
Q 键退出,并按 Y 保存配置。
生成配置文件
- 在
mr-library 目录下的命令行中,运行 python kconfig.py 脚本,自动生成 mr_config.h 配置文件。
添加包含路径与初始化
- 添加头文件路径:在编译器设置中添加
mr-library 的 include 目录路径。
- 配置自动初始化(GCC):在工程的链接脚本文件(如
link.ld)中添加以下代码段:
/* mr-library auto init */
. = ALIGN(4);
_mr_auto_init_start = .;
KEEP(*(SORT(.auto_init*)))
_mr_auto_init_end = .;
注:Keil、IAR等IDE若自动生成链接脚本,通常可跳过此步。
- 启用GNU语法(非GCC编译器):例如在Keil中,对于AC5或AC6编译器,需在选项设置中启用GNU扩展语法。
-
包含头文件与初始化:在工程的 main.c 文件中:
#include "include/mr_lib.h"
int main(void)
{
mr_auto_init(); // 框架自动初始化
// ... 用户代码
}
实践:点亮一个LED
#include "include/mr_lib.h"
/* 定义LED引脚(以STM32的PC13为例) */
#define LED_PIN_NUMBER 45
int main(void)
{
/* 自动初始化 */
mr_auto_init();
/* 打开PIN设备 */
int ds = mr_dev_open("pin", MR_OFLAG_RDWR);
/* 设置要控制的引脚号 */
mr_dev_ioctl(ds, MR_CTL_PIN_SET_NUMBER, mr_make_local(int, LED_PIN_NUMBER));
/* 设置引脚为推挽输出模式 */
mr_dev_ioctl(ds, MR_CTL_PIN_SET_MODE, mr_make_local(int, MR_PIN_MODE_OUTPUT));
while(1)
{
/* 点亮LED(输出高电平) */
mr_dev_write(ds, mr_make_local(uint8_t, MR_PIN_HIGH_LEVEL), sizeof(uint8_t));
mr_delay_ms(500);
/* 熄灭LED(输出低电平) */
mr_dev_write(ds, mr_make_local(uint8_t, MR_PIN_LOW_LEVEL), sizeof(uint8_t));
mr_delay_ms(500);
}
}
实践:输出“Hello World”
#include "include/mr_lib.h"
int main(void)
{
/* 自动初始化 */
mr_auto_init();
/* 打开Serial-1设备(假设为串口1) */
int ds = mr_dev_open("serial1", MR_OFLAG_RDWR);
/* 向串口输出 "Hello World" */
mr_dev_write(ds, "Hello World\r\n", sizeof("Hello World\r\n") - 1); // 注意长度计算
while(1);
}