mr-library 开源嵌入式驱动框架

黑洞算法
• 阅读 1476

背景

随着国产微控制器的崛起,市场上的微控制器种类越来越多。然而,以前的微控制器开发往往忽略了整体框架和程序分层,导致更换微控制器型号往往需要更改应用层代码,这使得开发工作变得繁重且乏味。常见的开发方式大多分为两种:
常见的开发方式通常分为两种:裸机编程和RTOS编程,由于两种方式的代码编写方式存在巨大差异,因此在两种方式之间切换意味着需要进行大规模的工程修改。
mr-library 的目标是帮助开发者提高开发效率和代码通用性,降低平台迁移的难度。

----------

mr-library 简介

mr-library 是一个嵌入式软件库,完全采用C语言编写,使用面向对象的设计方法,代码框架清晰,可以快速移植到不同的平台。它包括以下部分:

  • 内核层: mr-library 的核心部分,包含容器、对象、服务等。将各种对象注册到内核维护的容器中,使得应用更加高效有序。
  • 设备框架层: 提供统一的设备接口,将不同的设备接入到内核中。在应用层,仅需调用内核设备I/O接口即可访问设备。
  • 硬件驱动层: 为设备框架层设备提供必要的驱动,当硬件更换时仅修改驱动层。
  • 组件层: 通过内核提供的API实现不同的功能。包括但不限于虚拟文件系统、通用传感器模块、网络框架等。
  • 软件包: 可独立使用,无依赖的软件包。

    ----------

代码目录

mr-library 的代码目录结构如下表所示:

名称描述
bsp板级支持包
device设备文件
document文档
driver驱动文件
include库头文件
module组件
package软件包
src库源文件

----------

内核

内核中包含了容器、对象、服务等。

容器

容器负责统一管理注册到内核中的对象。

容器原型

struct mr_container
{
    struct mr_list list;                                            /* 容器链表 */

    enum mr_container_type type;                                    /* 容器类型 */
};
  • 容器链表:所有注册到容器的对象都将链接到容器链表上,当对象被移除容器时也将从容器链表上移除。
  • 容器类型:指定容器类型用以存放指定类型对象。

容器类型

内核维护了以下几类容器:

enum mr_container_type
{
    MR_CONTAINER_TYPE_MISC,                                         /* 杂类容器 */
    MR_CONTAINER_TYPE_DEVICE,                                       /* 设备容器 */
    MR_CONTAINER_TYPE_SERVER,                                       /* 服务容器 */
};

对象

对象原型

struct mr_object
{
    struct mr_list list;                                            /* 对象链表 */

    char name[MR_CONF_NAME_MAX + 1];                                /* 对象名 */
    mr_uint8_t flag;                                                /* 对象标志 */
};
  • 对象链表:用于将对象注册到容器中。
  • 对象名:对象的名称,同一容器不允许出现同名对象,不同容器允许对象重名。
  • 对象标志:用于标记对象状态。

对象操作接口

接口描述
mr_object_find从内核容器查找对象
mr_object_add添加对象到内核容器
mr_object_remove从内核容器移除对象
mr_object_move移动对象
mr_object_rename重命名对象

服务

事件服务

事件服务器是一种异步事件处理机制,它通过事件分发和回调的方式,可以有效地提高系统的异步处理能力、解耦性和可扩展性。

事件服务器包含两个主要组件:事件服务器和事件客户端。

  • 事件服务器用于接收和分发事件,它内部维护一个事件队列用于存储待处理事件和一个事件列表用于存储注册的事件客户端。
  • 事件客户端用于处理特定类型的事件,它需要注册到事件服务器并提供一个回调函数。

当事件发生时,事件服务器会将事件插入到其事件队列中进行缓存。事件服务器会周期性地从事件队列中取出事件进行分发,找到对应的事件客户端,然后调用其注册的回调函数进行事件处理。

事件服务原型

/* 事件服务器 */
struct mr_event_server
{
    struct mr_object object;                                        /**< 事件服务对象 */

    struct mr_fifo queue;                                           /**< 事件队列 */
    mr_avl_t list;                                                  /**< 事件链表 */
};

/* 事件客户端 */
struct mr_event_client
{
    struct mr_avl list;                                             /**< 事件链表 */

    mr_err_t (*cb)(mr_event_server_t server, void *args);           /**< 事件回调函数 */
    void *args;                                                     /**< 事件回调函数参数 */
};

事件服务操作接口

接口描述
mr_event_server_find从内核容器查找事件服务器
mr_event_server_add添加事件服务器到内核容器
mr_event_server_remove从内核容器移除事件服务器
mr_event_server_notify通知事件服务器事件发生
mr_event_server_handle事件服务器分发事件
mr_event_client_find从事件服务器查找事件客户端
mr_event_client_create创建事件客户端到事件服务器
mr_client_delete从事件服务器移除事件客户端

事件服务使用

实际开发中,可以将任务拆分后,分成一个一个事件,最终将单一任务事件合并到一个事件服务器中,交由事件服务器分发。

  • 裸机编程:将事件服务器放在主函数中运行,可任务异步执行。
  • RTOS:将不同任务的事件服务器放在不同线程中运行,可有效优化代码结构,减少线程数量,加速裸机代码移植。
事件服务使用示例:
/* 定义事件 */
#define EVENT1                          1
#define EVENT2                          2
#define EVENT3                          3

/* 定义事件服务器 */
struct mr_event_server event_server;

mr_err_t event1_cb(mr_event_server_t server, void *args)
{
    printf("event1_cb\r\n");

    /* 通知事件服务器事件2发生 */
    mr_event_server_notify(server, EVENT2);

    return MR_ERR_OK;
}

mr_err_t event2_cb(mr_event_server_t server, void *args)
{
    printf("event2_cb\r\n");

    /* 通知事件服务器事件3发生 */
    mr_event_server_notify(server, EVENT3);

    return MR_ERR_OK;
}

mr_err_t event3_cb(mr_event_server_t server, void *args)
{
    printf("event3_cb\r\n");

    return MR_ERR_OK;
}

int main(void)
{
    /* 添加事件服务器到内核容器 */
    mr_event_server_add(&event_server, "server", 4);

    /* 创建事件客户端到事件服务器 */
    mr_event_client_create(EVENT1, event1_cb, MR_NULL, &event_server);
    mr_event_client_create(EVENT2, event2_cb, MR_NULL, &event_server);
    mr_event_client_create(EVENT3, event3_cb, MR_NULL, &event_server);

    /* 通知事件服务器事件1发生 */
    mr_event_server_notify(&event_server, EVENT1);

    while (1)
    {
        mr_event_server_handle(&event_server);
    }
}

现象:

event1_cb
event2_cb
event3_cb

----------

设备

硬件抽象成设备,通过统一的设备操作接口进行交互。

设备原型

struct mr_device
{
    struct mr_object object;                                        /* 设备对象基类 */

    enum mr_device_type type;                                       /* 设备类型 */
    mr_uint16_t support_flag;                                       /* 设备支持的打开方式 */
    mr_uint16_t open_flag;                                          /* 设备状态 */
    mr_size_t ref_count;                                            /* 设备被引用次数 */
    void *data;                                                     /* 设备数据 */

    mr_err_t (*rx_cb)(mr_device_t device, void *args);              /* 设备接收回调函数 */
    mr_err_t (*tx_cb)(mr_device_t device, void *args);              /* 设备发送回调函数 */

    const struct mr_device_ops *ops;                                /* 设备操作方法 */
};
  • 设备支持的打开方式:设备只能以支持的打开方式打开。
  • 设备被引用次数:设备每被打开一次,引用+1,设备引用次数为0时设备关闭。
  • 设备数据:设备运行所需的数据。

设备类型

enum mr_device_type
{
    MR_DEVICE_TYPE_NONE,                                            /* 无类型设备 */
    MR_DEVICE_TYPE_PIN,                                             /* GPIO设备 */
    MR_DEVICE_TYPE_SPI_BUS,                                         /* SPI总线设备 */
    MR_DEVICE_TYPE_SPI,                                             /* SPI设备 */
    MR_DEVICE_TYPE_I2C_BUS,                                         /* I2C总线设备 */
    MR_DEVICE_TYPE_I2C,                                             /* I2C设备 */
    MR_DEVICE_TYPE_SERIAL,                                          /* UART设备*/
    MR_DEVICE_TYPE_ADC,                                             /* ADC设备 */
    MR_DEVICE_TYPE_DAC,                                             /* DAC设备 */
    MR_DEVICE_TYPE_PWM,                                             /* PWM设备 */
    MR_DEVICE_TYPE_TIMER,                                           /* TIMER设备 */
    MR_DEVICE_TYPE_FLASH,                                           /* FLASH设备 */
    /* ... */
};

设备操作方法

设备通过设备操作接口,最终会调用设备数据块中的设备操作方法。设备仅需实现设备打开方式所必须的方法即可。

struct mr_device_ops
{
    mr_err_t (*open)(mr_device_t device);
    mr_err_t (*close)(mr_device_t device);
    mr_err_t (*ioctl)(mr_device_t device, int cmd, void *args);
    mr_ssize_t (*read)(mr_device_t device, mr_off_t pos, void *buffer, mr_size_t size);
    mr_ssize_t (*write)(mr_device_t device, mr_off_t pos, const void *buffer, mr_size_t size);
};
方法描述
open打开设备,同时完成设备配置。仅当设备为首次被打开时,会调用此方法打开设备。
close关闭设备。仅当设备被所有用户关闭时(设备引用次数为0),会调用此方法关闭设备。
ioctl控制设备。根据cmd命令控制设备。
read从设备读取数据,pos是设备读取位置(不同设备所表示意义不同,请查看设备详细手册),size为设备读取字节大小。
write向设备写入数据,pos是设备写入位置(不同设备所表示意义不同,请查看设备详细手册),size为设备写入字节大小。

设备操作接口

接口描述
mr_device_add添加设备到内核容器
mr_device_find从内核容器查找设备
mr_device_open打开设备
mr_device_close关闭设备
mr_device_ioctl控制设备
mr_device_read从设备读取数据
mr_device_write向设备写入数据
GPIO设备使用示例:
/* 寻找PIN设备 */
mr_device_t pin_device = mr_device_find("pin");

/* 以可读可写的方式打开PIN设备 */
mr_device_open(pin_device, MR_OPEN_RDWR);

/* 配置B13引脚为推挽输出模式 */
struct mr_pin_config pin_config = { 29, MR_PIN_MODE_OUTPUT };
mr_device_ioctl(pin_device, MR_CTRL_CONFIG, &pin_config);

/* 设置B13为高电平 */
mr_uint8_t pin_level = 1;
mr_device_write(pin_device, 29, &pin_level, sizeof(pin_level));

/* 获取B13电平 */
mr_device_read(pin_device, 29, &pin_level, sizeof(pin_level));

/* 定义回调函数 */
mr_err_t pin_device_cb(mr_device_t device, void *args)
{
    mr_uint32_t *line = args;           /* 获取中断源 */
    
    /* 判断中断源是line-13 */
    if (*line == 13)
    {
        /* Do something */
    }
}

/* 绑定PIN函数回调函数 */
mr_device_ioctl(pin_device, MR_CTRL_SET_RX_CB, pin_device_cb);

----------

仓库链接https://gitee.com/MacRsh/mr-library.git

----------

许可协议

遵循 Apache License 2.0 开源许可协议,可免费应用于商业产品,无需公开私有代码。

----------

贡献代码

如果您对 mr-library 项目感兴趣,欢迎参与开发并成为代码贡献者。欢迎加入讨论群 199915649(QQ) 分享您的观点。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
10个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这