硬汉嵌入式论坛

 找回密码
 立即注册
查看: 79|回复: 2
收起左侧

[客户分享] 构想了一个增加型OSAL

[复制链接]

1

主题

19

回帖

22

积分

新手上路

积分
22
发表于 昨天 13:32 | 显示全部楼层 |阅读模式
前段时间做了一个资源占用比较大的东西、使用的是SWI中断触发NVCI中断自动出栈入栈的操作、相当于替代了RTOS的任务切换和优先级抢占的功能


但是那个是参考nordic的调度器实现的、每次调度是丢一个函数指事和一个事件指针到队列里、然后触发SWI中断、然后在SWI中断里把事件和函数指针取出来处理、

但是实际应用下来会发现很多时候事件指针是空的、只是丢了一个函数回调进去、但是这样却要占用一个比较大的事件队列和事件池

所以想了想能不能结合OSAL的优点改造一下、因为OSAL的事件触发其实就是不需要传递事件指针的。于事有了这样的设计。附件是让AI帮我生成的代码。但是还没空移植到真实的MCU环境上去运行。



#define MAX_SWI_TCB              3
#define MAX_OSAL_SUB_TASK 7

typedef int (*SubTask_Handler)(void *me,  uint8_t mIndex);

typedef struct{
      uint32_t*   pQuBuf;              //创建SWI_TCB_t 任务控制块的时候传入一个数组作为一个消息邮箱环形缓冲、必须使用无锁FIFO、最好是使用原子操作入队出队
      uint32_t    mQuSize;            //必是2^n
      
      //指定任务使用的SWI 中断号、主循环main也可以跑一个SWI_TCB_t任务控制块、
      //但是mSwiIrqNum设置成0xFF这样在触发事件的时候就不要触发SWI中断
      uint8_t      mSwiIrqNum;     
      uint8_t      mPreemptPrio;   //指定SWI中断的优先级分配
      uint8_t      mSubPrio;
}SWI_TCB_PARAM_t;

typedef struct{
      uint16_t mMsgType;  //所有消息头都是一个16位的消息类型、bit15~bit13保留和mEvtFlag一样的定义也用于区分事件发给谁
      uint16_t mCount;       //引用计数、用于广播消息的时候、内存块回收的时候通过统计这个引用计数检测消息是否被所有任务都处理完成
}MsgHead_t;

typedef struct {
     uint32_t*     pQuBuf;                //消息队列缓冲区 只是用来存放消息指针
     uint32_t      mQuIn;
     uint32_t      mQuOut;
     uint32_t      mQuSize;               //消息队列大小

     //移植到RTOS下时这个事件标志每个bit对应于需要被唤醒的子任务
     //例如bit0对应于mEvtFlag[0] 事件不为空、需要调用mSubTaskHook[0]处理
     //当移植到RTOS环境下时不再使用SWI中断、而是每个SWI_TCB_t任务控制块绑定一个RTOS任务
     RTOS_EVT_t          mRtosEvtFlag;
     //  
     // 裸机专用 1~7个任务独立事件标志、设置和清除事件标志的时候要用原子操作
     //  bit31 用于区分事件或消息是否广播模式、如果置1表示广播、则事件标志写入到mAdvEvtFlag、否则写入到mEvtFlag
     //  bit30 用于通知子任务有消息需要处理
     //  bit29~bit27 用于区分事件传递给那个子任务
     //  bit26 用于通知子任务有系统定时TICK需要处理
     //  bit25~0 APP子任务可以自由定义事件
     uint32_t                mEvtFlag[MAX_OSAL_SUB_TASK];
     //  广播的事件
     uint32_t                mAdvEvtFlag[MAX_OSAL_SUB_TASK];
     // 1~7个协作任务回调函数数组
     SubTask_Handler  mSubTaskHook[MAX_OSAL_SUB_TASK];
     // 1~7个协议作任务各自的事件订阅列表指针
     uint32_t*               pEvtSubscrTab[MAX_OSAL_SUB_TASK];  
     // 1~7个协议作任务每个定阅事件列表大小
     uint8_t                  mSubscrCountTab[MAX_OSAL_SUB_TASK];
     // SWI中断优先级配置
     uint8_t      mSwiIrqNum;
     uint8_t      mPreemptPrio;
     uint8_t      mSubPrio;
     uint8_t      mTaskAllocate;
}SWI_TCB_t;



typedef struct{
    SWI_TCB_t           * pOsalTcb;
    SubTask_Handler *pSubTaskHandle;       
    uint32_t*                pEvtSubscrTab;
    uint8_t                   mSubscrCount;
}SUB_TASK_PARAM_t;

// 协作任务私有控制块 不变
typedef struct {
     SWI_TCB_t  * pSwiTcb;
     uint8_t          mTaskSlot;            // 0~6 任务下标
     uint32_t        mTaskStatus;
     uint16_t        mDelayTick;
} SubTask_t;

//将所有SWI_TCB_t控制块的指针都填到这个表格里、广播事件和消息的时候从这个表里去找
//有公共的消息队列、所有任务获取广播数据从里获取
//用户可以选择用或不用定阅分发的机制、通过宏控制
typedef struct{
     uint32_t*     pQuBuf;               
     uint32_t      mQuIn;
     uint32_t      mQuOut;
     uint32_t      mQuSize;        
     SWI_TCB_t * mObservers[MAX_SWI_TCB];     
     uint8_t         mTcbAllocate;  
}SWI_TCB_SUBJECT_t;

int swi_osaL_run(SWI_TCB_t * pOsalTcb)  //SWI中断里检查每个mEvtFlag是否为不为空、并且调用mSubTaskHook处理事件或消息
{

     for(uint8_t i=0; i<; i++)
     {
           if(pOsalTcb->mEvtFlag[i] != 0x00000000)
           {
                   if(pOsalTcb->mSubTaskHook[i] != NULL)
                   {
                         pOsalTcb->mSubTaskHook(pOsalTcb,  i);
                   }
           }
     }
     
}

bool swi_osal_init(SWI_TCB_t * pOsalTcb, SWI_TCB_PARAM_t * pParam);

//创建任务的时候SWI_TCB_t * pOsalTcb = pParam->pOsalTcb;
//创建子任务时分配任务编号 pTask->mTaskSlot = pOsalTcb->mTaskAllocate++;
//同时把任务定阅的事件列表 填写到 pOsalTcb->pEvtSubscrTab[pTask->mTaskSlot] = pParam->pEvtSubscrTab;
int Task create_cotask(SubTask_t* pTask, SUB_TASK_PARAM_t *pParam);

//  传入的事件标志的时候最高5bit不可使用、或者cotask_send_evt函数内通过掩码自动屏蔽掉最高的几个bit
//  因为需要通过pTask->mTaskSlot设置 bit29~bit27 用于区分事件传递给那个子任务、bit31必须置0
//  同时通过pTask->pSwiTcb 找到任务控制块并设置相对应的 mEvtFlag 同时触发对应SWI中断、产生任务调度
int cotask_send_evt( SubTask_t* pTask,  uint32_t mEvt);

// 消息使用内存池动态分配、然后发送给指定的pTask->pSwiTcb->pQuBuf消息缓冲、
// 同时利用pTask->mTaskSlot 设置 bit29~bit27 和 bit30 设置相对应的 mEvtFlag、和mMsgType同时触发对应SWI中断、产生任务调度
//
int cotask_send_msg(SubTask_t* Task,  void *pMsg);

//子任务内部最好是用cotask_send_evt和cotask_send_msg、
//而为了解偶合、跨模块可以用cotask_publish_evt和cotask_publish_msg 这样不同模块之间完全不需要知道对方是谁
//发送广播事件和广播消息的时候把bit31置1、发送广播消息的时候把mMsgType的bit32和bit30都置1
int cotask_publish_evt(uint32_t mEvt);
//发送广播消息的时候把消息的引用计数清零
int cotask_publish_msg(void *pMsg);
//初始化定阅分发系统
int init_tcb_subject(void);
//将任务控制块添加进入定阅分发列表里
int add_tcb_to_subject(SWI_TCB_t * pOsalTcb);

//从SWI_TCB_SUBJECT_t消息队列取出消息、分发给mObservers里的每个任务控制块、同时把消息引用计数++
//消息分发完成后按SWI_TCB_t里的SWI中断优先级触发中断
int tcb_subject_run(void);

uint32_t       sOsalQuBuf0[];
uint32_t       sOsalQuBuf1[];
uint32_t       sOsalQuBuf2[];

SWI_TCB_t;  s_OsalTcb0;   //放在main循环中使用
SWI_TCB_t;  s_OsalTcb1;   //放在SWI1中断使用
SWI_TCB_t;  s_OsalTcb2;   //放在SWI2中断使用

uint32_t       s_SubscrTab_0_0[];
uint32_t       s_SubscrTab_0_1[];
SubTask_t    s_SubTak_0_0;
SubTask_t    s_SubTak_0_1;

uint32_t       s_SubscrTab_1_0[];
uint32_t       s_SubscrTab_1_1[];
SubTask_t    s_SubTak_1_0;
SubTask_t    s_SubTak_1_1;

uint32_t       s_SubscrTab_2_0[];
uint32_t       s_SubscrTab_2_1[];
SubTask_t    s_SubTak_2_0;
SubTask_t    s_SubTak_2_1;

int sub_task_0_0_handle((void *me,  uint8_t mIndex)
{
       ..............................
}

int sub_task_0_1_handle((void *me,  uint8_t mIndex)
{
       ..............................
}

int sub_task_1_0_handle((void *me,  uint8_t mIndex)
{
       SWI_TCB_t * pOsalTcb = (SWI_TCB_t * )me;
      //根据索引找到事件标志
       uint32_t mEvt = pOsalTcb->mEvtFlag[mIndex];

      //任务事件处理、处理一个事件标志位后就清除对应的事件标志位
      //如果是在RTOS环境下对应的mEvtFlag所有bit都清空后还需要清空mRtosEvtFlag相应的标志位
      ..............................
      //例如想要在当前子任务给task_2_0发送事件、就会触发抢占、假定SWI2中断优先级设置比SWI1中断优先级高一些
      cotask_send_evt(&s_SubTak_2_0,  .....);
      //发送消息也是一样的
}

int sub_task_1_1_handle((void *me,  uint8_t mIndex)
{
       ..............................
}

int sub_task_2_0_handle((void *me,  uint8_t mIndex)
{
       ..............................
}

int sub_task_2_1_handle((void *me,  uint8_t mIndex)
{
       ..............................
}

int main(void)
{
    SWI_TCB_PARAM_t     mTcbParam;
    SUB_TASK_PARAM_t   mTaskParam;

    mTcbParam.pQuBuf   = sOsalQuBuf0;  //初始化消息缓冲和大小
    mTcbParam.mQuSize =
    mTcbParam.mSwiIrqNum =  0xFF;       //指定SWI中断号0xFF、表示发送事件、消息的时候不需要触发SWI中断
    ...................................
    swi_osal_init(&s_OsalTcb0,  &mTcbParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb0;
    mTaskParam.pSubTaskHandle   = sub_task_0_0_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_0_0;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_0_0)/4;
    Task create_cotask(&s_SubTak_0_0, &mTaskParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb0;
    mTaskParam.pSubTaskHandle   = sub_task_0_1_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_0_1;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_0_1)/4;
    Task create_cotask(&s_SubTak_0_1, &mTaskParam);

    mTcbParam.pQuBuf   = sOsalQuBuf1;  //初始化消息缓冲和大小
    mTcbParam.mQuSize =
    mTcbParam.mSwiIrqNum =  ; //分配SWI中断号和优先级
    ...................................
    swi_osal_init(&s_OsalTcb1,  &mTcbParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb1;
    mTaskParam.pSubTaskHandle   = sub_task_1_0_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_1_0;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_1_0)/4;
    Task create_cotask(&s_SubTak_1_0, &mTaskParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb1;
    mTaskParam.pSubTaskHandle   = sub_task_1_1_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_1_1;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_1_1)/4;
    Task create_cotask(&s_SubTak_1_1, &mTaskParam);
   
    mTcbParam.pQuBuf = sOsalQuBuf2;
    ...................................
    swi_osal_init(&s_OsalTcb2,  &mTcbParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb2;
    mTaskParam.pSubTaskHandle   = sub_task_2_0_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_2_0;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_2_0)/4;
    Task create_cotask(&s_SubTak_2_0, &mTaskParam);

    mTaskParam.pOsalTcb               = &s_OsalTcb2;
    mTaskParam.pSubTaskHandle   = sub_task_2_1_handle;
    mTaskParam.pEvtSubscrTab      = s_SubscrTab_2_1;
    mTaskParam.mSubscrCount      = sizeof(s_SubscrTab_2_1)/4;
    Task create_cotask(&s_SubTak_2_1, &mTaskParam);

     init_tcb_subject();
     add_tcb_to_subject(&s_OsalTcb0);
     add_tcb_to_subject(&s_OsalTcb1);
     add_tcb_to_subject(&s_OsalTcb2);
     
     while(1)
     {
          swi_osaL_run(&s_OsalTcb0);
          tcb_subject_run();                  //这个放在主循环里合适吗?还是说放在优先级最高的SWI中断里
     }
}

void swi_1_irq(void)
{
    swi_osaL_run(&s_OsalTcb1);
}

void swi_2_irq(void)
{
    swi_osaL_run(&s_OsalTcb2);
}


osal_pro_max_v3.c

15.94 KB, 下载次数: 0

osal_pro_max_v3.h

5.61 KB, 下载次数: 0

osal_test_v3.c

5.63 KB, 下载次数: 0

回复

使用道具 举报

1

主题

19

回帖

22

积分

新手上路

积分
22
 楼主| 发表于 昨天 14:03 | 显示全部楼层
OSAL 事件分配指导原则(修正版)
1. 核心设计:私有事件 vs 广播事件
类型        存储数组        作用域        位值含义是否统一
私有事件        mEvtFlag[slot]        单个子任务私有        不同子任务可重用相同位值,互不干扰
广播事件        mAdvEvtFlag[slot]        系统全局(所有TCB)        同一事件位在全局必须有统一的语义,不可冲突
关键点:私有事件位和广播事件位可以完全重叠,因为系统调度时会分别通过 osal_task_evt_get 和 osal_task_adv_get 获取,并在回调中分开处理。
例如:

子任务A 私有事件 bit0 表示“按键按下”

子任务B 私有事件 bit0 表示“数据就绪”

广播事件 bit0 表示“系统紧急停止”

三者可以同时存在,互不影响。

2. 事件位布局(32位)
位范围        用途        用户是否可用
bit31        系统标记广播事件(EVT_BROADCAST)        &#10060; 禁止使用
bit30        消息事件(EVT_MSG_PENDING)        &#10060; 禁止使用
bit29–27        点对点事件槽位编码(EVT_SLOT_MASK,EVT_MAKE宏使用)        &#9888;&#65039; 避免直接使用
bit26        系统Tick事件(EVT_TICK)        &#9888;&#65039; 若系统使用则保留
bit25–0        用户自定义事件        &#9989; 完全可用
说明:bit29–27 仅当使用 EVT_MAKE(slot, userEvt) 构造事件时才会占用,直接调用 osal_task_send_evt 或 osal_publish_evt 时不会影响这3位。为避免混乱,用户事件宏应使用 bit25–0 或其他未使用的低位。

3. 私有事件分配规则(每个子任务独立)
每个子任务可以自由定义自己的私有事件位,无需与其他子任务协调。

私有事件位可以重用:子任务1 的 bit0 和子任务2 的 bit0 可以表示完全不同的业务事件。

禁止跨子任务直接使用私有事件进行通信(如需通信,应使用广播事件或点对点发送消息/事件)。

私有事件只能由该子任务自身接收和处理(通过 osal_task_send_evt 发送给指定子任务)。

私有事件定义示例
c
// 子任务A 私有事件
#define TASK_A_EVT_DATA_READY   (1 << 0)
#define TASK_A_EVT_TIMEOUT      (1 << 1)

// 子任务B 私有事件(重用 bit0、bit1)
#define TASK_B_EVT_BUTTON       (1 << 0)
#define TASK_B_EVT_UART_RX      (1 << 1)
4. 广播事件分配规则(系统全局统一)
广播事件位必须在整个系统范围内统一规划,所有模块对同一事件位的理解必须一致。

广播事件位不能与其它广播事件位冲突(即同一个位不能表示两种不同含义)。

广播事件位可以与私有事件位重叠(因为存储在不同数组中)。

广播事件订阅列表中的值就是该事件位本身(不含 EVT_BROADCAST 标志)。

广播事件定义示例
c
// 全局广播事件(所有模块统一理解)
#define ADV_EVT_SENSOR_ALARM    (1 << 16)
#define ADV_EVT_NETWORK_UP      (1 << 17)
#define ADV_EVT_POWER_FAIL      (1 << 18)

// 订阅广播事件(在创建子任务时指定)
const uint32_t mySubscriptions[] = { ADV_EVT_SENSOR_ALARM, ADV_EVT_NETWORK_UP };
5. 系统保留事件位
宏        位值        说明        用户能否使用
EVT_BROADCAST        (1UL << 31)        系统内部标记广播事件,osal_publish_evt 自动添加        &#10060;
EVT_MSG_PENDING        (1UL << 30)        消息事件,由消息队列自动触发,用户不应主动发送或清除        &#10060;
EVT_TICK        (1UL << 26)        系统Tick事件(若用户调用 osal_tick_handler 并发送)        &#9888;&#65039; 可选
EVT_SLOT_MASK        0x38000000        点对点槽位编码(EVT_MAKE 内部使用)        &#9888;&#65039; 避免
建议:如果应用不使用系统Tick,bit26 也可作为用户事件使用,但需注意与未来可能的系统功能兼容。

6. 冲突避免核心规则
&#9989; 必须遵守
绝对不可以使用 bit31 和 bit30。

不要直接发送或清除 EVT_MSG_PENDING(由消息队列内部管理)。

广播事件位必须在项目范围内统一规划,不能在不同模块中赋予不同含义。

订阅广播事件时,订阅列表中只能包含纯事件位,不能包含 EVT_BROADCAST 标志。

&#9888;&#65039; 强烈建议
私有事件使用低位(如 bit0–bit15),广播事件使用高位(如 bit16–bit25)——虽然不是必须,但可以提高代码可读性,避免混淆。

为每个子任务的私有事件单独命名空间(例如 TASK1_EVT_xxx、TASK2_EVT_xxx),避免阅读代码时误解。

不要将私有事件位值直接用于 osal_publish_evt;反之,也不要将广播事件位值直接用于 osal_task_send_evt。

&#128161; 最佳实践
回调函数中分别处理私有和广播事件:

c
int my_callback(void *me, uint8_t idx) {
    SWI_TCB_t *tcb = (SWI_TCB_t*)me;
    uint32_t priv = osal_task_evt_get(tcb, idx);
    uint32_t adv  = osal_task_adv_get(tcb, idx);

    if (priv & MY_PRIV_EVT) {
        // 处理私有事件
        osal_task_evt_clear(tcb, idx, MY_PRIV_EVT);
    }
    if (adv & ADV_EVT_SYSTEM_ALERT) {
        // 处理广播事件
        osal_task_adv_clear(tcb, idx, ADV_EVT_SYSTEM_ALERT);
    }
    return 0;
}
为广播事件建立全局头文件,供所有模块共享;私有事件定义放在各自模块内部。           看来回头得把广播事件搞成64位的
回复

使用道具 举报

1

主题

19

回帖

22

积分

新手上路

积分
22
 楼主| 发表于 昨天 15:03 | 显示全部楼层
增加了一个消息还回队列的接口、当子任务从消息队列取出来消息后发现不是给自己的消息、就需要还回去但是不增加引用计数。消息类型在分配的时候也是同样按照事件标志一样的原则给自己分配3个bit用来告知子任务、这个消息是否是自己的。

osal_pro_max_v5.c

17.23 KB, 下载次数: 0

osal_pro_max_v5.h

5.83 KB, 下载次数: 0

osal_test_v5.c

5.96 KB, 下载次数: 0

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2026-5-22 00:03 , Processed in 0.225147 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表