打印
[蓝牙芯片]

深入探索南京沁恒WCH BLE蓝牙芯片的TMOS:个人学习之旅

[复制链接]
734|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-6-5 11:10 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、前言
近期我们的产品中用到了南京沁恒推出的低功耗蓝牙芯片CH582。这款芯片与常见的STM32的ARM Cortex-M处理器架构存在显著差异,而是采用了新兴的指令集架构----RISC-V。其中,CH582的TMOS任务管理系统给我留下了深刻的印象。尽管它与传统的操作系统有一些相似之处,但又有其独特之处。

TMOS任务管理系统是CH582的一大亮点。它类似于操作系统的概念,能够实现任务的调度、内存管理等功能。然而,与常见的操作系统相比,TMOS更加轻量级,旨在满足低功耗、实时响应和高能效的需求。这种设计使得CH582在处理任务时具有出色的性能和效率,同时保持了较低的功耗。

二、TMOS是什么
1.介绍
蓝牙要与多个设备连接并实现多功能和多任务,这就会导致了调度问题。尽管软件和协议栈可以扩展,但最底层的执行单元只有一个。为了处理多事件和多任务切换,需要将事件和任务对应起来。为此,TMOS被引入作为一个操作系统抽象层。

TMOS是调度的核心,BLE协议栈、profile定义和所有应用都围绕它实现。TMOS不同于传统的操作系统,它是一个允许软件建立和执行事件的循环。

举例来说,TMOS是通过时间片轮询的方式实现多任务调度运行,实际上每次只有一个任务运行。系统时钟来源于芯片RTC,单位为625us。用户通过注册任务(Task)将自定义的事件(Event)添加到TMOS的任务链表中,由TMOS进行调度运行。Event事件标志位,为1则运行,为0则不运行。

2.工作机制
任务注册:用户可以通过注册任务将自定义的事件添加到TMOS的任务链表中。每个任务在注册后分配一个唯一的ID。每个任务最多包含16个事件,其中包括一个消息事件和15个自定义事件。事件采用BitMap的方式定义事件标志,如0x0001、0x0002、0x0004等。
任务调度:TMOS循环查询任务链表,根据任务ID确定优先级,优先级越低,任务越先运行。每个任务运行完一个事件后,通过异或的方式清除已运行的事件,同时返回未运行的事件标志,然后运行下一个任务。当任务调度系统运行一遍后,再次回到任务链表头的一个事件,如此循环下去。
时间片轮询:TMOS通过时间片轮询的方式实现多任务调度运行。系统时钟单位为625us,以RTC为基准得到所有需要系统的时间。每个任务占用一定的时间(独占式,执行完当前任务退出,继续查询其他可执行任务),所有的任务通过时间分片的方式处理。
综上所述,TMOS通过任务注册、任务调度和时间片轮询的方式实现多任务的调度和管理。这种机制可以有效地实现多事件和多任务的切换,提高系统的效率和响应速度。

三、使用步骤
1.MounRiver Studio
这是一款面向RISC-V,ARM等内核MCU集成开发环境,界面如下,感兴趣的可以自行下载。



2.注册任务ID
ID定义是全局变量。

代码如下(示例):

uint8_t ROPE_TaskID = INVALID_TASK_ID; // Task ID for internal task/event processing

ROPE_TaskID = TMOS_ProcessEventRegister(ROPE_ProcessEvent); // 向系统注册了一个任务


/**
* @brief   register process event callback function//注册处理事件回调函数
*
* @param   eventCb-events callback function//事件回调函数
*
* @return  0xFF - error,others-task id//错误,其他-任务id
*/
extern tmosTaskID TMOS_ProcessEventRegister( pTaskEventHandlerFn eventCb );


3.任务初始化
编写任务初始化进程,并需要添加到TMOS初始化进程中,这就是说系统启动后不能动态添加功能(新的Task ID);

代码如下(示例):

    CH58X_BLEInit();
    HAL_Init();
    ReadImageFlag();
    rope_init();//用户定义初始化
    GAPRole_PeripheralInit();
    Peripheral_Init();


4.编写任务处理程序



代码如下(示例):

/*********************************************************************
* @fn      Peripheral_ProcessEvent
*
* @brief   Peripheral Application Task event processor.  This function
*          is called to process all events for the task.  Events
*          include timers, messages and any other user defined events.
*
* @param   task_id - The TMOS assigned task ID. TMOS分配的任务ID
* @param   events - events to process.  This is a bit map and can
*                   contain more than one event.
*
* @return  events not processed
*/
uint16_t ROPE_ProcessEvent(uint8_t task_id, uint16_t events)
{
    if (events & SYS_EVENT_MSG)
    {
        uint8_t *pMsg;
        if ((pMsg = tmos_msg_receive(ROPE_TaskID)) != NULL)
        {
            tmos_msg_deallocate(pMsg);
        }
        return (events ^ SYS_EVENT_MSG);
    }
    ........省略
}



5.定义任务事件
事件名按位定义,每一层taskID最多包含1个消息事件和15个任务事件(共16位)

代码如下(示例):

#define READ_BAT_EVENT   1
#define READ_UART_EVENT  2
#define E1000MS_EVENT    4
#define E100MS_EVENT     8
#define E10MS_EVENT      16
#define SLEEP_EVENT      20


6.启动任务
6.1 立即启动
代码如下(示例):

/**
* @brief   start a event immediately
*
* @param   taskID - task ID of event
* @param   event - event value
*
* @return  0 - SUCCESS.
*/
extern bStatus_t tmos_set_event( tmosTaskID taskID, tmosEvents event );//立即启动事件


tmos_set_event(ROPE_TaskID, READ_UART_EVENT);//立刻执行


6.2 延迟启动
代码如下(示例):

/**
* @brief   start a event after period of time
*
* @param   taskID - task ID to set event for
* @param   event - event to be notified with
* @param   time - timeout value
*
* @return  TRUE,FALSE.
*/
extern BOOL tmos_start_task( tmosTaskID taskID, tmosEvents event, tmosTimer time );//在一段时间后开始一个事件


tmos_start_task(ROPE_TaskID, E100MS_EVENT, 80); //80 * 0.625ms后执行一次  50ms
1
7.任务循环
时基函数循环

        while (1)
        {                                 
                if (stSysTime.**._10ms + TEN_MILLISECOND < Time_millis()) //10ms
                {
                        stSysTime.**._10ms = Time_millis();
                        //用户代码                                       
                }
                if (stSysTime.**._50ms + FIFTY_MILLISECOND < Time_millis()) //50ms
                {
                        stSysTime.**._50ms = Time_millis();
                        //用户代码                       
                }
                if (stSysTime.**._100ms + BEST_MILLISECOND < Time_millis()) //100ms
                {
                        stSysTime.**._100ms = Time_millis();       
                        //用户代码
                }
                if (stSysTime.**._1s + THOUSAND_MILLISECOND < Time_millis()) //1s
                {
                        stSysTime.**._1s = Time_millis();
                        //用户代码               
            }
    }



其实这个任务循环写法和我们写时基函数循环执行有点相似。

uint16_t ROPE_ProcessEvent(uint8_t task_id, uint16_t events)
{
  if(events & ID事件1)
  {
     //用户代码
     tmos_start_task(ID, ID事件1, 执行时间1);
     return (events ^ ID事件1);
  }
  if(events & ID事件2)
  {
     //用户代码
     tmos_start_task(ID, ID事件2, 执行时间2);
     return (events ^ ID事件2);
  }  
  if(events & ID事件3)
  {
     //用户代码
     tmos_start_task(ID, ID事件3, 执行时间3);
     return (events ^ ID事件3);
  }
  if(events & ID事件4)
  {
     //用户代码
     tmos_start_task(ID, ID事件4, 执行时间4);
     return (events ^ ID事件4);
  }   
}



具体代码如下(示例):

/*********************************************************************
  @fn      ROPE_ProcessEvent
  @brief   Peripheral Application Task event processor.  This function
           is called to process all events for the task.  Events
           include timers, messages and any other user defined events.
@param   task_id - The TMOS assigned task ID. TMOS分配的任务ID
  @param   events - events to process.  This is a bit map and can
                    contain more than one event.
  @return  events not processed
*/
uint16_t ROPE_ProcessEvent(uint8_t task_id, uint16_t events)
{
    if (events & SYS_EVENT_MSG)
    {
        uint8_t *pMsg;
        if ((pMsg = tmos_msg_receive(ROPE_TaskID)) != NULL)
        {
            tmos_msg_deallocate(pMsg);
        }
        return (events ^ SYS_EVENT_MSG);
    }
    if (events & READ_UART_EVENT)
    {
        if (rope.u8Uart** == rope.u8RxNum && rope.u8RxNum)
        {
            analy_recv(rope.u8RxBuf, rope.u8RxNum);
            memset(rope.u8RxBuf, 0, 100);
            rope.u8RxNum = 0;
        }
        else
        {
            rope.u8Uart** = rope.u8RxNum;
        }
        tmos_start_task(ROPE_TaskID, READ_UART_EVENT, 8); // 8 * 0.625ms执行一次  2.5MS
        return (events ^ READ_UART_EVENT);
    }
    if (events & E1000MS_EVENT)
    {
        tmos_start_task(ROPE_TaskID, E1000MS_EVENT, 1600); // 1600 * 0.625ms执行一次  1000MS
        off_power_task();
        return (events ^ E1000MS_EVENT);
    }
    if (events & E100MS_EVENT)
    {
        tmos_start_task(ROPE_TaskID, E100MS_EVENT, 40); // 40 * 0.625ms执行一次  25MS
        if(IO_KEY_POWER.read() == 1)
        {
            app_bat_gather();
            app_led_display();
            app_adc_action();
        }
        return (events ^ E100MS_EVENT);
    }
    if (events & E10MS_EVENT)
    {
        tmos_start_task(ROPE_TaskID, E10MS_EVENT, 16); // 16 * 0.625ms执行一次  10MS
        key_scan();
        return (events ^ E10MS_EVENT);
    }
}




主循环不停调用TMOS_SystemProcess,查询可执行event事件;如果开始HAL_SLEEP,芯片开启低功耗睡眠模式,Tmos会开启RTC唤醒功能,事件被执行前会自动唤醒,运行事件代码。

/*********************************************************************
  @fn      Main_Circulation
  @brief   主循环
  @return  none
*/
__HIGH_CODE
__attribute__((noinline))
void Main_Circulation()
{
    while(1)
    {
        TMOS_SystemProcess();
    }
}


8.注意事项
禁止在中断中调用
建议不要在单个任务中执行超过连接间隔一半时长的任务,否则将影响蓝牙通讯
同理,在中断中建议不要执行超过连接间隔一半时长的任务,否则将影响蓝牙通讯
在事件生效执行的代码中调用延时执行函数时,延时时间以当前事件生效时间点为基准偏移,所以对调用延时执行函数在生效执行的代码中摆放的位置没有要求。
任务存在优先级,根据在xxx_ProcessEvent函数中判断的先后顺序决定,同时生效的任务,先执行先判断,后执行后判断。注意,执行完先判断的事件任务后,要等到任务调度系统轮巡一遍后,才会执行后判断的事件任务。
事件名按位定义,每一层taskID最多包含1个消息事件和15个任务事件(共16位)
四、总结
以上就是我对南京沁恒WCH TMOS的个人学习总结,本人能力有限,如有错误,还请见谅指出。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_44836335/article/details/135182921

使用特权

评论回复
沙发
yangxiaor520| | 2024-6-9 16:57 | 只看该作者
沁恒的各种接口转换芯片还是不错的

使用特权

评论回复
板凳
gouguoccc| | 2024-6-10 17:36 | 只看该作者
其实可以做成AT指令的蓝牙芯片

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1498

主题

14375

帖子

8

粉丝