bos/core目录代码详细介绍

main函数内容

main函数中,用户首先初始化MCU的时钟、提供给bos使用的心跳定时器以及外设资源(GPIO、UART等)。

然后调用bInit(),最后在while(1)中调用 bExec(); 以下是这两个函数所做的操作:

main_uml

core.c和device.c都是设备的操作,看这两个代码之前,需要先看设备注册和device.h的内容。

BOS设备注册

设备注册,在b_device_list.h文件中,将需要注册的设备信息依次写在h文件中即可:

 //注册信息的格式如下:设备号,驱动号,描述
 //B_DEVICE_REG(dev_1, bDriverNumber_t, "description")
 //设备号:用户自定义
 //驱动号,以下是目前支持的
  /**
typedef enum
{
    B_DRIVER_NULL = 0,
    B_DRIVER_24CXX,
    B_DRIVER_DS18B20,
    B_DRIVER_ESP12F,
    B_DRIVER_FM25CL,
    B_DRIVER_ILI9320,
    B_DRIVER_ILI9341,
    B_DRIVER_KEY,
    B_DRIVER_LIS3DH,
    B_DRIVER_MATRIXKEYS,
    B_DRIVER_MCUFLASH,
    B_DRIVER_OLED,
    B_DRIVER_PCF8574,
    B_DRIVER_RS485,
    B_DRIVER_SD,
    B_DRIVER_SPIFLASH,
    B_DRIVER_SSD1289,
    B_DRIVER_ST7789,
    B_DRIVER_TESTFLASH,
    B_DRIVER_XPT2046,
    B_DRIVER_NUMBER
} bDriverNumber_t;
*/

设备的注册信息填写完成后,在device.c和device.h会生成对应的数据:

typedef enum
{
    B_REG_DEV_NULL,
#define B_DEVICE_REG(dev, driver, desc) dev,
#include "b_device_list.h"
    B_REG_DEV_NUMBER
} bDeviceName_t;

static bDriverNumber_t bDriverNumberTable[B_REG_DEV_NUMBER] = {
    B_DRIVER_NULL,
#define B_DEVICE_REG(dev, driver, desc) driver,
#include "b_device_list.h"
};

static const char *bDeviceDescTable[B_REG_DEV_NUMBER] = {
    "null",
#define B_DEVICE_REG(dev, driver, desc) desc,
#include "b_device_list.h"
};

默认会存在一个null设备,主要是为了防止用户未注册任何设备的时候编译报错。了解上面数据是怎么来的后,开始看每个函数。

b_core负责管理fd以及提供使用fd操作设备的接口。b_device负责通过设备号找到对应的驱动实例,进而调用对应接口。

core_device_uml

bos/core/b_core.c

// 根据设备的数量创建fd的数组
typedef struct
{
    uint8_t  flag;    // 操作标志,BCORE_FLAG_R or BCORE_FLAG_W or BCORE_FLAG_RW
    uint8_t  status;  // 设备状态 默认状态 BCORE_STA_NULL,打开状态 BCORE_STA_OPEN
    uint32_t number;  // 设备号
    uint32_t lseek;   // 偏移量,用于读写
} bCoreFd_t;
static bCoreFd_t bCoreFdTable[B_REG_DEV_NUMBER];
// 定义轮询的段
bSECTION_DEF_FLASH(bos_polling, pbPoling_t);

// 会添加一个monitor的轮询至轮询段,预留着
static void _bCoreMonitor(void);
BOS_REG_POLLING_FUNC(_bCoreMonitor);


// 创建fd
// 调用bOpen时会调用该函数创建fd返回
static int _bCoreCreateFd(uint32_t dev_no, uint8_t flag)
{
    int            i    = 0;
    int            fd   = -1;
    static uint8_t init = 0;
    if (init == 0)
    {
        // 首次调用时,初始化数组里面的status字段。
        for (i = 0; i < B_REG_DEV_NUMBER; i++)
        {
            bCoreFdTable[i].status = BCORE_STA_NULL;
        }
        init = 1;
    }
    
    // 首先判断当前设备是否已经被打开,如果有被打开的操作,则返回
    for (i = 0; i < B_REG_DEV_NUMBER; i++)
    {
        if (bCoreFdTable[i].status == BCORE_STA_OPEN)
        {
            if (dev_no == bCoreFdTable[i].number)
            {
                return -1;
            }
        }
    }
    
    // 找到一个未被使用的句柄,对其进行赋值
    // 默认偏移为0,状态改为 BCORE_STA_OPEN
    for (i = 0; i < B_REG_DEV_NUMBER; i++)
    {
        if (bCoreFdTable[i].status == BCORE_STA_NULL)
        {
            bCoreFdTable[i].flag   = flag;
            bCoreFdTable[i].number = dev_no;
            bCoreFdTable[i].status = BCORE_STA_OPEN;
            bCoreFdTable[i].lseek  = 0;
            fd                     = i;
            break;
        }
    }
    return fd;
}

// 删除句柄。bClose会调用。
static int _bCoreDeleteFd(int fd)
{
    if (fd < 0)
    {
        return -1;
    }
    // 删除操作,只是将状态再改回 BCORE_STA_NULL
    if (bCoreFdTable[fd].status == BCORE_STA_NULL)
    {
        return -1;
    }
    bCoreFdTable[fd].status = BCORE_STA_NULL;
    return 0;
}

// 打开设备,打开设备的时候传入设备号
int bOpen(uint32_t dev_no, uint8_t flag)
{
    int fd     = -1;
    int retval = 0;
    if (!IS_VALID_FLAG(flag))
    {
        return -1;
    }
    // 参数检查没问题后,获取一个fd
    fd = _bCoreCreateFd(dev_no, flag);
    if (fd < 0)
    {
        return -1;
    }
    // 调用调用open的执行函数,
    retval = bDeviceOpen(dev_no);
    if (retval >= 0 || retval == B_DEVICE_FUNC_NULL)
    {
        return fd;
    }
    // 执行Open失败,则将fd给去掉。
    _bCoreDeleteFd(fd);
    return -1;
}
// 读写函数的流程大体一致
int bRead(int fd, uint8_t *pdata, uint32_t len)
{
    int retval;
    if (fd < 0 || fd >= B_REG_DEV_NUMBER || pdata == NULL)
    {
        return -1;
    }

    if (!READ_IS_VALID(bCoreFdTable[fd].flag) || bCoreFdTable[fd].status == BCORE_STA_NULL)
    {
        return -1;
    }
    // 判断参数没问题后,调用read执行函数。传入fd里面的设备号和偏移
    retval = bDeviceRead(bCoreFdTable[fd].number, bCoreFdTable[fd].lseek, pdata, len);
    if (retval >= 0)
    {
        // 读的函数返回值必须是实际读取数据的多少
        // 根据返回值调整偏移量
        bCoreFdTable[fd].lseek += len;
    }
    return retval;
}

// 调整偏移
int bLseek(int fd, uint32_t off)
{
    if (fd < 0 || fd >= B_REG_DEV_NUMBER)
    {
        return -1;
    }
    // 只有在打开的状态下才能调整
    if (bCoreFdTable[fd].status == BCORE_STA_NULL)
    {
        return -1;
    }
    bCoreFdTable[fd].lseek = off;
    return 0;
}

// 控制指令+参数。每个驱动支持的指令在b_driver_cmd.h
int bCtl(int fd, uint8_t cmd, void *param);

// 关闭操作
int bClose(int fd)
{
    if (fd < 0 || fd >= B_REG_DEV_NUMBER)
    {
        return -1;
    }
    
    if (bCoreFdTable[fd].status == BCORE_STA_NULL)
    {
        return -1;
    }
    // 参数的判断合法后,执行close操作
    bDeviceClose(bCoreFdTable[fd].number);
    // 将使用的fd回收
    return _bCoreDeleteFd(fd);
}

// 这里是提供查询,没有任何设备处于打开状态
int bCoreIsIdle();

// 初始化,其做的操作可以参考本页最上面的图
int bInit();

// 如果首次初始化失败,可以调用这个接口重写执行一次
// 例如可以单独控制某个设备的电源,其首次初始化失败后,可以对其重写上电,然后执行重写初始化操作
int bReinit(uint32_t dev_no)
{
    return bDeviceReinit(dev_no);
}

// 挨个执行段内的函数
int bExec()
{
    bSECTION_FOR_EACH(bos_polling, pbPoling_t, polling)
    {
        (*polling)();
    }
    return 0;
}

/**
 * \brief  eg. bModifyHalIf(OLED, sizeof(bOLED_HalIf_t),
 *                           (uint8_t)(&(((bOLED_HalIf_t *)0)->_if._i2c.dev_addr)),
 *                            &dev_addr, 1);
 * 修改设备硬件接口的参数。例如IIC的硬件接口,程序运行过程中,修改硬件接口里的地址
 */
int bModifyHalIf(uint32_t dev_no, uint32_t type_size, uint32_t off, const uint8_t *pval,
                 uint8_t len)
{
    if ((off + len) > type_size || pval == NULL || len == 0 || type_size == 0)
    {
        return -1;
    }
    return bDeviceModifyHalIf(dev_no, off, pval, len);
}

// 这个会根据fd找到设备使用的驱动,判断驱动里面的读写缓存是否有数据
uint8_t bFdIsReadable(int fd);
uint8_t bFdIsWritable(int fd);

// 这个会根据fd找到设备使用的驱动,判断驱动里状态字段
uint8_t bFdIsAbnormal(int fd);

bos/core/b_device.c

这里最重要的是将设备和驱动实例结合。注册设备时只是指定了设备号和驱动号,并没有指定驱动实例。

device_uml

// 驱动号数组,可以根据设备号在这里查询到驱动号
static bDriverNumber_t bDriverNumberTable[B_REG_DEV_NUMBER] = {
    B_DRIVER_NULL,
#define B_DEVICE_REG(dev, driver, desc) driver,
#include "b_device_list.h"
};

static const char *bDeviceDescTable[B_REG_DEV_NUMBER] = {
    "null",
#define B_DEVICE_REG(dev, driver, desc) desc,
#include "b_device_list.h"
};

// 根据设备的数量创建对应数量的驱动实例。
static bDriverInterface_t bDriverInterfaceTable[B_REG_DEV_NUMBER];

// 设备初始化
int bDeviceInit()
{
    uint32_t i = 0, j = 0;
    // 驱动实例数据全部清0
    memset(bDriverInterfaceTable, 0, sizeof(bDriverInterfaceTable));
    
    bSECTION_FOR_EACH(driver_init_0, bDriverRegInit_t, pdriver_init_0)
    {
        // 取出初始化段的信息
        j = 0;  // 例如注册了2个 24c02,那么序号来区分,j则是用来计算序号
        for (i = 0; i < B_REG_DEV_NUMBER; i++)
        {
            // 遍历驱动号数组,驱动号匹配则对驱动实例进行初始化
            if (bDriverNumberTable[i] == pdriver_init_0->drv_number)
            {
                bDriverInterfaceTable[i].drv_no = j++;   // 赋值序号
                bDriverInterfaceTable[i].pdes   = bDeviceDescTable[i];  // 描述
                // 执行初始化
                bDriverInterfaceTable[i].status = pdriver_init_0->init(&bDriverInterfaceTable[i]);
            }
        }
    }
    // 初始化有两个段,进行的操作和上面一样
    bSECTION_FOR_EACH(driver_init, bDriverRegInit_t, pdriver_init)
    {
        j = 0;
        for (i = 0; i < B_REG_DEV_NUMBER; i++)
        {
            if (bDriverNumberTable[i] == pdriver_init->drv_number)
            {
                bDriverInterfaceTable[i].drv_no = j++;
                bDriverInterfaceTable[i].pdes   = bDeviceDescTable[i];
                bDriverInterfaceTable[i].status = pdriver_init->init(&bDriverInterfaceTable[i]);
            }
        }
    }
    // 打印 设备号和设备状态
    b_log("\r\ndev_no\t\t%16s\tstate\r\n", "des");
    for (i = 0; i < B_REG_DEV_NUMBER; i++)
    {
        if (bDriverInterfaceTable[i].pdes == NULL)
        {
            b_log("%d\t\t%16s\t%d\r\n", i, "no drive", -1);
            continue;
        }
        b_log("%d\t\t%16s\t%d\r\n", i, bDriverInterfaceTable[i].pdes,
              bDriverInterfaceTable[i].status);
    }
    b_log("\r\n");
    return 0;
}

// 以下接口流程大致一样,根据设备号定位到驱动实例,然后执行对应的函数。
int bDeviceReinit(uint32_t dev_no);
int bDeviceOpen(uint32_t dev_no);
int bDeviceClose(uint32_t dev_no);
int bDeviceRead(uint32_t dev_no, uint32_t offset, uint8_t *pdata, uint32_t len);
int bDeviceWrite(uint32_t dev_no, uint32_t offset, uint8_t *pdata, uint32_t len);
int bDeviceCtl(uint32_t dev_no, uint8_t cmd, void *param);

// 根据设备号定位到驱动实例,返回驱动实例的状态字段
int bDeviceISNormal(uint32_t dev_no)
{
    if (dev_no >= B_REG_DEV_NUMBER)
    {
        return -1;
    }
    b_log("%s :%d\r\n", bDeviceDescTable[dev_no], bDriverInterfaceTable[dev_no].status);
    return bDriverInterfaceTable[dev_no].status;
}

// 根据设备号定位到驱动实例,读取和设置_private字段的内容
int bDeviceReadMessage(uint32_t dev_no, bDeviceMsg_t *pmsg);
int bDeviceWriteMessage(uint32_t dev_no, bDeviceMsg_t *pmsg);

// 驱动可以配置读、写缓存。本质是一个FIFO实例。判断可读/可写依据FIFO的数据长度
uint8_t bDeviceIsWritable(uint32_t dev_no)
uint8_t bDeviceIsReadable(uint32_t dev_no)
{
    uint8_t  retval = 0;
    uint16_t len    = 0;
    if (dev_no >= B_REG_DEV_NUMBER)
    {
        return retval;
    }
    if (bDriverInterfaceTable[dev_no].read == NULL)
    {
        return retval;
    }
    // FIFO的buf为空则表示没有读取缓存,返回1,表示可以执行读操作
    if (bDriverInterfaceTable[dev_no].r_cache.pbuf == NULL)
    {
        retval = 1;
    }
    else
    {
        // 当有读缓存时,则必须读缓存里有数据才返回 1
        bFIFO_Length(&bDriverInterfaceTable[dev_no].r_cache, &len);
        retval = (len > 0);
    }
    return retval;
}

队列、信号量、任务和定时器这几个操作的流程类似,基本步骤如下:

// 使用宏创建实例
B_XXXX_CREATE_ATTR(name, ...)
// 调用创建函数
bXXXXCreate(..., &name)
// 根据返回的id去调用其他接口

创建的实例都会挂在链表上,是确保调用接口传入的id是合法的(在链表中找得到)

bos/core/b_queue.c

队列的数据结构如下:

typedef struct
{
    uint32_t         number;
    uint32_t         r_index;
    uint32_t         w_index;
    uint32_t         msg_count;
    uint32_t         msg_size;
    void            *mq_mem;
    uint32_t         mq_size;
    struct list_head list;
} bQueueAttr_t;

q_uml

// 不会使用malloc去申请空间,所以在创建队列前用宏定义一个静态变量
#define B_QUEUE_CREATE_ATTR(attr_name, pbuf, buf_size) \
    static bQueueAttr_t attr_name = {.mq_mem = pbuf, .mq_size = buf_size}
bQueueId_t bQueueCreate(uint32_t msg_count, uint32_t msg_size, bQueueAttr_t *attr)
{
    if (attr == NULL || attr->mq_mem == NULL || (attr->mq_size < (msg_count * msg_size)))
    {
        return NULL;
    }
    // 如果已经在队列中存在,则直接返回
    if (_bQueueFind(attr) != NULL)
    {
        return attr;
    }
    attr->number    = 0;
    attr->r_index   = 0;
    attr->w_index   = 0;
    attr->msg_count = msg_count;
    attr->msg_size  = msg_size;
    // 赋值后添加到链表
    list_add(&attr->list, &bQueueListHead);
    return attr;
}
// 往队列里写入和读取数据都是采用拷贝的方式,这个是非阻塞的,返回0则是成功
int bQueuePutNonblock(bQueueId_t id, const void *msg_ptr);
int bQueueGetNonblock(bQueueId_t id, void *msg_ptr);

// 配合pt一起使用,可以等待,直到写入和读取成功。超时则退出。
#define B_QUEUE_PUT(pt, queue_id, msg_ptr, timeout) \
    PT_WAIT_UNTIL((pt), bQueuePutNonblock((queue_id), (msg_ptr)) == 0, (timeout))

#define B_QUEUE_GET(pt, queue_id, msg_ptr, timeout) \
    PT_WAIT_UNTIL((pt), bQueueGetNonblock((queue_id), (msg_ptr)) == 0, (timeout))

bos/core/b_sem.c

typedef struct
{
    uint32_t         value;
    uint32_t         value_max;
    struct list_head list;
} bSemAttr_t;

创建的信号量都挂在链表上,关键的变量只有两个,初始值和最大值。

// 创建信号量,不使用malloc,需要先通过宏创建静态变量
#define B_SEM_CREATE_ATTR(attr_name) static bSemAttr_t attr_name = {.value = 0}
bSemId_t bSemCreate(uint32_t max_count, uint32_t initial_count, bSemAttr_t *attr)
{
    if (attr == NULL)
    {
        return NULL;
    }
    // 查找是否在已经在链表中存在
    if (_bSemFind(attr) != NULL)
    {
        return attr;
    }
    attr->value     = initial_count;
    attr->value_max = max_count;
    // 赋值后添加到链表
    list_add(&attr->list, &bSemListHead);
    return attr;
}

// 获取信号量,不阻塞,成功则返回0
int bSemAcquireNonblock(bSemId_t id)
{
    bSemAttr_t *attr = NULL;
    if (id == NULL)
    {
        return -1;
    }
    // 查找是否在链表中
    attr = _bSemFind(id);
    if (attr == NULL || attr->value == 0)
    {
        return -1;
    }
    attr->value -= 1;
    return 0;
}

// 配合PT一起使用,等待bSemAcquireNonblock的操作成功,超时则退出
#define B_SEM_ACQUIRE(pt, sem_id, timeout) \
    PT_WAIT_UNTIL((pt), bSemAcquireNonblock((sem_id)) == 0, (timeout))

任务和定时器,其节点里都有对应的执行函数,其调用关系如下:

task_timer_uml

bos/core/b_task.c

typedef char (*bTaskFunc_t)(struct pt *pt, void *arg);

typedef struct
{
    const char      *name;
    bTaskFunc_t      func;
    void            *arg;
    uint8_t          enable;
    struct pt        task_pt;
    struct list_head list;
} bTaskAttr_t;

任务是基于pt来做,主要是用来处理异步的场景。创建的任务挂在链表上,循环处理各个任务的执行函数。

任务执行函数的模板:

/**
PT_THREAD(test_task)(struct pt *pt, void *arg)
{
    PT_BEGIN(pt);
    while (1)
    {
        //.....
    }
    PT_END(pt);
}
*/
// 将 _bTaskCore 放入轮训的段,由bExec()调用。
// 在这里依次调用链表里任务的执行函数。
static void _bTaskCore()
{
    struct list_head *pos   = NULL;
    bTaskAttr_t      *pattr = NULL;
    list_for_each(pos, &bTaskListHead)
    {
        // 遍历链表里各个节点
        pattr = list_entry(pos, bTaskAttr_t, list);
        if (pattr != NULL && pattr->func != NULL && pattr->enable == 1)
        { 
            // 存在执行函数,且当前任务没有被挂起,则调用执行函数
            pCurrentTaskAttr = pattr;
            pattr->func(&pattr->task_pt, pattr->arg);
        }
    }
}

BOS_REG_POLLING_FUNC(_bTaskCore);

同样的套路,先使用宏创建静态变量,不使用malloc。

#define B_TASK_CREATE_ATTR(attr_name) \
    static bTaskAttr_t attr_name = {.name = NULL, .func = NULL, .arg = NULL}
bTaskId_t bTaskCreate(const char *name, bTaskFunc_t func, void *argument, bTaskAttr_t *attr)
{
    if (attr == NULL || func == NULL)
    {
        return NULL;
    }
    // 查询是否已经存在于链表中
    if (_bTaskFind(attr) != NULL)
    {
        return attr;
    }
    attr->name   = (name == NULL) ? B_TASK_DEFAULT_NAME : name;
    attr->func   = func;
    attr->arg    = argument;
    attr->enable = 1;   // 默认使能
    // 使用PT_INIT初始化 task_pt
    PT_INIT(&attr->task_pt);
    // 添加到链表
    list_add(&attr->list, &bTaskListHead);
    return attr;
}

bos/core/b_timer.c

typedef void (*bTimerFunc_t)(void *arg);

/// Timer type.
typedef enum
{
    B_TIMER_ONCE     = 0,  ///< One-shot timer.
    B_TIMER_PERIODIC = 1   ///< Repeating timer.
} bTimerType_t;

typedef struct
{
    bTimerFunc_t     func;
    void            *arg;
    uint8_t          enable;
    uint32_t         tick;
    uint32_t         cycle;
    bTimerType_t     type;
    struct list_head list;
} bTimerAttr_t;

定时器代码内容和task内容比较类似。

// 将 _bTimerCore放入轮训的段,由bExec()调用。
// 在这里依次判断每个定时器是否满足触发条件,满足则调用执行函数。
static void _bTimerCore()
{
    struct list_head *pos   = NULL;
    bTimerAttr_t     *pattr = NULL;
    list_for_each(pos, &bTimerListHead)
    {
        // 遍历链表
        pattr = list_entry(pos, bTimerAttr_t, list);
        if (pattr != NULL && pattr->func != NULL && pattr->enable == 1)
        {
            if (bHalGetSysTick() - pattr->tick > MS2TICKS(pattr->cycle))
            {
                // 判断是否满足触发条件,满足则执行处理函数
                pattr->func(pattr->arg);
                // 根据定时器的类型,决定接下来怎么做。
                // 单次定时,执行完处理函数后停止
                // 周期定时器,则是更新tick
                if (pattr->type == B_TIMER_ONCE)
                {
                    pattr->enable = 0;
                }
                else
                {
                    pattr->tick = bHalGetSysTick();
                }
            }
        }
    }
}

BOS_REG_POLLING_FUNC(_bTimerCore);

创建定时器,同样是先使用宏创建静态变量后,再调用创建定时器的接口。

#define B_TIMER_CREATE_ATTR(attr_name) static bTimerAttr_t attr_name = {.func = NULL, .arg = NULL}
bTimerId_t bTimerCreate(bTimerFunc_t func, bTimerType_t type, void *argument, bTimerAttr_t *attr)
{
    if (attr == NULL || func == NULL)
    {
        return NULL;
    }
    // 判断是否已经在链表中存在
    if (_bTimerFind(attr) != NULL)
    {
        return attr;
    }
    attr->func   = func;
    attr->arg    = argument;
    attr->type   = type;
    attr->enable = 0; // 默认是停止
    // 添加到链表
    list_add(&attr->list, &bTimerListHead);
    return attr;
}