DA14531学习笔记-软件架构(1)


本文参考 UM-B-119_DA14585-DA14531_SW_Platform_Reference,因为本人水平有限,文中不免有遗漏或错误之处,还请谅解和指正。

环境描述

名称 环境描述
操做系统 Windows10专业工做站版 ,版本号1909
SoC DA14531
SDK 6.0.12.1020.2
Keil μVision V5.15
参考文件 UM-B-119_DA14585-DA14531_SW_Platform_Reference

软件架构

DA15431和其余的BLE协议栈的架构差很少,先看下BLE协议栈的架构,关于协议栈的内容另外的文章说明。
BLE协议栈
下图是DA14531/DA14585/586 SoC的软件架构
DA14531/585/586软件架构图
上图能够看出这些内容:html

名称 内容
Application User application
SDK application
Profiles GATT Services
GATT Clients
prf_utils
Host GATT(通用属性配置文件)
ATT(属性协议)
ATTDB(属性协议数据库)
SMP(安全管理协议)
GAP(通用访问协议)
L2CAP(逻辑链路控制和适配协议)
S/W BLE Controler LL Manger(链路层管理)
Kernel Riviera Waves许可的小型高效实时内核
Main loop & System Software 主循环函数和系统函数
Peripheral & radio Divers 外设和Radio驱动

Main loop &System software

打开SDK文件的ble_examples中任意一个Keil工程web

文件名 函数名 描述
sdk_arch int main(void) 主函数

  系统启动时,会进入到main函数初始化系统并进入主循环,检查BLE是否处于活动状态,若是是,则为内核调度程序提供CPU时间以处理全部未决消息和事件,接下来,查询用户应用程序是否要执行与消息无关的任务。若是内核和用户应用程序都无事可作,则迅速过渡到低功耗模式并等待中断再次开始。main loop不会随用户程序而更改。
  提供了一组回调,用于通知应用程序主循环的状态。对于这些回调,咱们常用术语“异步执行”来将它们与内核调度程序中生成的其余事件和回调区分开来,这称为同步执行。应用程序可使用回调的返回值或经过相应地设置睡眠设置来部分控制主循环。在这里重要的是要理解,当BLE处于活动状态时,将从主循环中调用内核调度程序。若是咱们不将控制权授予主循环,或者BLE未激活,则内核消息的处理将延迟。数据库

//main 函数代码
int main(void)
{
    sleep_mode_t sleep_mode;
    // initialize retention mode
    init_retention_mode();
    //global initialise
    system_init();
    /* ************************************************************************************ * Platform initialization ************************************************************************************ */
    while(1)
    {
        do {
            // schedule all pending events 安排全部未解决的事件
            schedule_while_ble_on();
        }
        while (app_asynch_proc() != GOTO_SLEEP);    //grant control to the application, try to go to power down
                                                    //if the application returns GOTO_SLEEP
        //wait for interrupt and go to sleep if this is allowed
        if (((!BLE_APP_PRESENT) && (check_gtl_state())) || (BLE_APP_PRESENT))
        {
            //Disable the interrupts
            GLOBAL_INT_STOP();
            app_asynch_sleep_proc();
            // get the allowed sleep mode
            // time from rwip_power_down() to __WFI() must be kept as short as possible!!
            sleep_mode = rwip_power_down();
            if ((sleep_mode == mode_ext_sleep) || (sleep_mode == mode_ext_sleep_otp_copy))
            {
                //power down the radio and whatever is allowed 关闭radio和任何容许关闭的电源
                arch_goto_sleep(sleep_mode);
                // In extended sleep mode the watchdog timer is disabled
                // (power domain PD_SYS is automatically OFF). However, if the debugger
                // is attached the watchdog timer remains enabled and must be explicitly
                // disabled.
				//在扩展睡眠模式下,看门狗定时器被禁用(电源域PD_SYS自动关闭)。 可是,若是链接了调试器,则看门狗定时器将保持启用状态,而且必须显式禁用。
                if ((GetWord16(SYS_STAT_REG) & DBG_IS_UP) == DBG_IS_UP)
                {
                    wdg_freeze();    // Stop watchdog timer
                }
                //wait for an interrupt to resume operation 等待中断以恢复操做
                __WFI();
               if ((GetWord16(SYS_STAT_REG) & DBG_IS_UP) == DBG_IS_UP)
                {
                    wdg_resume();    // Resume watchdog timer 恢复看门狗定时器
                }
                //resume operation 恢复操做
                arch_resume_from_sleep();
            }
            else if (sleep_mode == mode_idle)
            {
                if (((!BLE_APP_PRESENT) && check_gtl_state()) || (BLE_APP_PRESENT))
                {
                    //wait for an interrupt to resume operation
                    __WFI();
                }
            }
            // restore interrupts
            GLOBAL_INT_START();
        }
        wdg_reload(WATCHDOG_DEFAULT_PERIOD);
    }
}

未决消息和事件安全

do {
 // schedule all pending events 安排全部未解决的事件
  schedule_while_ble_on();
  }
 while (app_asynch_proc() != GOTO_SLEEP);    //grant control to the application, try to go to power down
                                              //if the application returns GOTO_SLEEP

  SoC在上电的时候调用 schedule_while_ble_on() 函数,这个函数的主要做用是检查BLE Clock是否使能,若是使能了,调用rwip_schedule() 来处理未解决的事件和消息,经过查找,并无发现函数的原型,推测是rw内核系统封装的函数接口。
  等待中断,若是容许的话会进入睡眠模式,禁用系统全局中断,app_asynch_sleep_proc() 这个函数的做用是在睡眠检查开始以前更新应用程序的状态。后续的代码应该都是关于低功耗的一些操做,这里有关于看门狗的一些说明,在调试的时候,看门狗必须是保持开启状态,若是是进入了低功耗模式,那么看门狗会被关闭。具体能够看函数注释。架构

peripheral &radio drivers

  这部分其实都是SoC的一些外设和射频的一些驱动,好比SPI、I2C、USART等等。具体能够看SDK中的sdk_driver 里的函数。app

Kernel 内核

  DA14531/585/586用的都是Riviera Waves许可的小型高效实时内核。提供如下几个功能。能够参考这篇文章:RW内核和消息处理机制或者是《RW-BT-KERNEL-SW-FS.pdf》这个指导文件。dom

  • 任务建立和状态转换
  • 任务之间的消息交换
  • 计时器管理
  • 动态内存分配
  • BLE事件调度和处理

调度

  内核的核心是运行在应用程序主循环中的调度程序。调度程序检查是否设置了事件,并调用相应的处理程序为未决事件提供服务。该事件能够是BLE或计时器事件,也能够是两个任务之间的消息。
  调度程序从BLE核心硬件获取任什么时候序信息。主循环代码可确保在BLE核心的硬件模块不处于睡眠模式时不执行内核调度程序。
  内核的实现位于ROM存储区中。所以,SDK发行版中不包含源代码文件。API类型的定义和API函数的原型能够在如下头文件中找到。关于内核的详细接口将在另外的文章详细介绍。异步

API 说明
ke_task.h 内核任务管理和建立
ke_msg.h 消息处理
ke_mem.h 动态内存分配
ke_timer.h 计时器建立和删除API

任务

  BLE堆栈层/ GATT配置文件/主机应用程序等都被实例化为内核的任务,该任务一般在系统初始化时建立。单个BLE应用程序中支持的最大任务数为23
  每一个任务都有惟一一个任务ID,任务结构体类型为struct ke_task_desc,这个结构体在ke_task.hasync

/// Task descriptor grouping all information required by the kernel for the scheduling.
struct ke_task_desc
{
    /// Pointer to the state handler table (one element for each state).
    //应用程序每一个状态的消息处理程序
    const struct ke_state_handler* state_handler;
    /// Pointer to the default state handler (element parsed after the current state).
    //默认消息处理程序
    const struct ke_state_handler* default_handler;
    /// Pointer to the state table (one element for each instance).
    //当前任务状态
    ke_state_t* state;
    /// Maximum number of states in the task.
    //任务的最高有效状态
    uint16_t state_max;
    /// Maximum index of supported instances of the task.
    //任务实例的最大数量
    uint16_t idx_max;
};

ke_task.h头文件中声明了全部API函数以及内核任务建立和管理的类型。ide

类型名称 描述
ke_msg_handler 消息处理程序结构
ke_state_handler 特定或默认状态的消息处理程序列表
ke_task_desc 任务描述符
custom_msg_handler 自定义消息处理程序
函数名 功能
void ke_task_init(void) 初始化内核任务模块
ke_task_create 建立一个任务
ke_task_delete 删除一个任务
ke_state_get 检索任务的状态(获取任务的状态)
ke_state_set 设置由其任务ID标识的任务的状态
ke_msg_discard 通用消息处理程序,在不处理任务的状况下使用消息
ke_msg_save 通用消息处理程序,在不处理任务的状况下使用消息。?待确认
ke_task_msg_flush 此函数将刷新全部消息,这些消息当前在内核中针对特定任务的待处理状态。

动态内存分配

RW内核给应用程序提供了动态内存分配的API,定义了四个堆存储区。

堆存储区名称 做用
KE_MEM_ENV 用于环境变量的内存分配
KE_MEM_ATT_DB 用于ATT协议数据库,即服务,特征,属性
KE_MEM_KE_MSG 用于内核消息的内存分配
KE_MEM_NON_RETENTION 通用堆内存。Note

堆内存的大小能够分为自动肯定和用户肯定,二者的分别位于如下文件。

种类 文件位置
自动肯定 da1458x_scatter_config.h
用户肯定 da1458x_config_advanced.h

  这些堆中任何一个的动态内存分配都是经过调用该ke_malloc()函数来完成的。堆内存的大小和选择在函数的参数中传递。若是所选堆内存中的内存空间不足,则内核内存管理代码将尝试在另外一个堆中分配请求的内存空间。若是内核的全部堆内存中的内存分配失败,则会发出系统软件重置信息。
  内核提供了两个API函数

函数名 做用
ke_malloc() 分配请求的内存空间
ke_msg_free() 在请求的内存地址释放分配的内存空间

消息

内核提供了一种在任务之间交换消息的机制。 内核交换的消息具备特定的格式。 格式由ke_msg.h中定义的struct ke_msg类型肯定。 ke_msg结构包括如下成员:

结构体成员 说明
Id 一个包含消息标识的16位无符号整数。 十个最低有效位造成一个序列号,该序列号在任务的消息中是惟一的。 六个最高有效位是任务的ID,以确保系统中消息标识的惟一性。 宏KE_BUILD_ID可用于构建符合此约定的消息ID。
dest_id 消息的目标任务的任务ID
src_id 消息源任务的任务ID
param_len 参数中包含的消息数据的大小。
param 消息数据的占位符。 结构成员的类型是32位无符号整数的一个位置表。 可是,分配的内存空间的大小由堆内存肯定,堆内存由消息内存分配函数分配,而且等于param_len

消息传输分为三个步骤:
一、 消息发送者调用下面两个宏中的一个来进行消息内存的分配
   ① KE_MSG_ALLOCKE_MEM_KE_MSG 鼠标移到这里为消息分配堆内存中的空间。

/// Kernel memory heaps types.
enum
{
    /// Memory allocated for environment variables
    KE_MEM_ENV,
    /// Memory allocated for Attribute database
    KE_MEM_ATT_DB,
    /// Memory allocated for kernel messages
    KE_MEM_KE_MSG,
    /// Non Retention memory block
    KE_MEM_NON_RETENTION,
    KE_MEM_BLOCK_MAX,
};

消息ID,源和目标任务ID以及消息数据的类型在函数的参数中传递。函数根据数据类型计算要分配的内存空间。返回指向已分配消息的数据开头的指针。详见下面代码块

/** **************************************************************************************** 2. @brief Convenient wrapper to ke_msg_alloc() 3. 4. This macro calls ke_msg_alloc() and cast the returned pointer to the 5. appropriate structure. Can only be used if a parameter structure exists 6. for this message (otherwise, use ke_msg_send_basic()). 7. 8. @param[in] id Message identifier 9. @param[in] dest Destination Identifier 10. @param[in] src Source Identifier 11. @param[in] param_str parameter structure tag 12. 13. @return Pointer to the parameter member of the ke_msg. **************************************************************************************** */
#define KE_MSG_ALLOC(id, dest, src, param_str) \ (struct param_str*) ke_msg_alloc(id, dest, src, sizeof(struct param_str))

   ② KE_MSG_ALLOC_DYN :与KE_MSG_ALLOC相似,在KE_MSG_ALLOC的入口参数中加入了长度,这个长度是预数据类型大小相同的附加内存大小。详见下面代码块

/** **************************************************************************************** * @brief Convenient wrapper to ke_msg_alloc() * * This macro calls ke_msg_alloc() and cast the returned pointer to the * appropriate structure with a variable length. Can only be used if a parameter structure exists * for this message (otherwise, use ke_msg_send_basic()).Can only be used if the data array is * located at the end of the structure. * * @param[in] id Message identifier * @param[in] dest Destination Identifier * @param[in] src Source Identifier * @param[in] param_str parameter structure tag * @param[in] length length for the data * * @return Pointer to the parameter member of the ke_msg. **************************************************************************************** */
#define KE_MSG_ALLOC_DYN(id, dest, src, param_str,length) (struct param_str*)ke_msg_alloc(id, dest, src, \ (sizeof(struct param_str) + length));

二、填写消息参数。 源任务的代码应填写消息的数据
三、将消息结构推送到内核中。

   调用函数 ke_msg_send() 将消息发送到目标任务。由KE_MSG_ALLOCKE_MSG_ALLOC_DYN返回的指针必须在函数的参数中传递。若是消息已分配但未发送,则必须调用ke_msg_free() 来释放已经分配的内存。
   经过在消息的任务描述符(ke_task_desc )中定义消息处理程序功能(结构体ke_msg_handler),能够实现发送到任务的消息的接收。 当目标任务使用消息时,状态处理程序应返回KE_MSG_CONSUMED,而将消息转发到另外一个任务时,状态处理程序应返回KE_MSG_FREE。 函数 ke_msg_forward() 必须用于此操做。

计时器

   DA14531提供定时器服务的事件建立和删除。是以BLE硬件内核的BLE_GROSS_TIMER为基础的,BLE_GROSS_TIMER计时器的精度为10毫秒。 请求计时器事件的任务将收到有关计时器到期的消息的通知。 消息ID等于用于计时器建立的计时器ID。 所以,计时器ID必须是有效的消息ID。 还必须在任务处理程序列表中定义计时器处理程序功能。 内核计时器是一次性计时器。主要有如下两个API函数

API 说明
app_timer_set() 其具体实现函数是ke_time_set()函数,以10毫秒为单位,最大有效超时为4194300,至关于699分钟
ke_timer_delete() 删除活动的内核计时器