本文主要是介绍DA14531学习笔记-软件架构(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 环境描述
- 软件架构
- Main loop &System software
- peripheral &radio drivers
- Kernel 内核
- 调度
- 任务
- 动态内存分配
- 消息
- 计时器
本文参考 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协议栈的架构,关于协议栈的内容另外的文章说明。
下图是DA14531/DA14585/586 SoC的软件架构
上图可以看出这些内容:
名称 | 内容 |
---|---|
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工程
文件名 | 函数名 | 描述 |
---|---|---|
sdk_arch | int main(void) | 主函数 |
系统启动时,会进入到main函数初始化系统并进入主循环,检查BLE是否处于活动状态,如果是,则为内核调度程序提供CPU时间以处理所有未决消息和事件,接下来,查询用户应用程序是否要执行与消息无关的任务。如果内核和用户应用程序都无事可做,则迅速过渡到低功耗模式并等待中断再次开始。main loop不会随用户程序而更改。
提供了一组回调,用于通知应用程序主循环的状态。对于这些回调,我们经常使用术语“异步执行”来将它们与内核调度程序中生成的其他事件和回调区分开来,这称为同步执行。应用程序可以使用回调的返回值或通过相应地设置睡眠设置来部分控制主循环。在这里重要的是要理解,当BLE处于活动状态时,将从主循环中调用内核调度程序。如果我们不将控制权授予主循环,或者BLE未激活,则内核消息的处理将延迟。
//main 函数代码
int main(void)
{sleep_mode_t sleep_mode;// initialize retention modeinit_retention_mode();//global initialisesystem_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 allowedif (((!BLE_APP_PRESENT) && (check_gtl_state())) || (BLE_APP_PRESENT)){//Disable the interruptsGLOBAL_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 interruptsGLOBAL_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 里的函数。
Kernel 内核
DA14531/585/586用的都是Riviera Waves许可的小型高效实时内核。提供以下几个功能。可以参考这篇文章:RW内核和消息处理机制或者是《RW-BT-KERNEL-SW-FS.pdf》这个指导文件。
- 任务创建和状态转换
- 任务之间的消息交换
- 计时器管理
- 动态内存分配
- 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.h
/// 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函数以及内核任务创建和管理的类型。
类型名称 | 描述 |
---|---|
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 |
消息传输分为三个步骤:
1、 消息发送者调用下面两个宏中的一个来进行消息内存的分配
① KE_MSG_ALLOC :KE_MEM_KE_MSG 鼠标移到这里为消息分配堆内存中的空间。
/// Kernel memory heaps types.
enum
{/// Memory allocated for environment variablesKE_MEM_ENV,/// Memory allocated for Attribute databaseKE_MEM_ATT_DB,/// Memory allocated for kernel messagesKE_MEM_KE_MSG,/// Non Retention memory blockKE_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 the5. appropriate structure. Can only be used if a parameter structure exists6. for this message (otherwise, use ke_msg_send_basic()).7. 8. @param[in] id Message identifier9. @param[in] dest Destination Identifier10. @param[in] src Source Identifier11. @param[in] param_str parameter structure tag12. 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));
2、填写消息参数。 源任务的代码应填写消息的数据
3、将消息结构推送到内核中。
调用函数 ke_msg_send() 将消息发送到目标任务。由KE_MSG_ALLOC或KE_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() | 删除活动的内核计时器 |
这篇关于DA14531学习笔记-软件架构(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!