bos/core目录代码详细介绍
main函数内容
main函数中,用户首先初始化MCU的时钟、提供给bos使用的心跳定时器以及外设资源(GPIO、UART等)。
然后调用bInit(),最后在while(1)中调用 bExec(); 以下是这两个函数所做的操作:
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负责通过设备号找到对应的驱动实例,进而调用对应接口。
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
这里最重要的是将设备和驱动实例结合。注册设备时只是指定了设备号和驱动号,并没有指定驱动实例。
// 驱动号数组,可以根据设备号在这里查询到驱动号
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;
// 不会使用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))
任务和定时器,其节点里都有对应的执行函数,其调用关系如下:
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;
}