diff --git a/CHANGELOG.md b/CHANGELOG.md index 4709421..866e234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ## [0.1.0] - 2020-03-18 + ### Added + - add changelog and release support + ### Changed + - clean code, and move all tests to test directory +## [0.2.0] - 2020-03-22 +### Added + +- add hal_api for uart/nvram/ctrls/sens diff --git a/README.md b/README.md index 3e7887a..7fbd525 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,366 @@ -## simulink library for Hardware Abstract Layer(HAL) and code-gen support +# slhal 硬件无关层(HAL)设计与实现 -[![pipeline status](http://sh.matthewgong.com/matt/slhal/badges/master/pipeline.svg)](http://sh.matthewgong.com/matt/slhal/commits/master) +[![pipeline status](https://sh.matthewgong.com/matt/slhal/badges/master/pipeline.svg)](https://sh.matthewgong.com/matt/slhal/commits/master) -本Simulink库提供了用于与飞控硬件接口的代码生成模块。只需要以template目录下的*_wrapper.c文件为模板编写接口文件,即可实现飞控代码的移植。 +本Simulink库对飞控连接的各种设备进行抽象,提出一套硬件无关层标准化接口,使得上层simulink飞控开发者无需关心具体硬件。 + +## 设计思想概述 + +### 使用S-function Builder协助生成接口模板 + +HAL的接口模块都是masked S-function。为了免去手工编写.c/.tlc代码引入错误,项目使用了S-function Builder协助生成接口模板。 + +每个HAL接口库文件都有对应的`*_bld.slx`库,其用于存放对应的s-function builder的模块。 +所有s-function模块的的PW、输入、输出、参数等接口定义都由图形化定义生成,大大简化了编写s-function的过程,自动生成了标准的mex接口代码、TLC代码完全无需修改,只需要关注产生的包裹函数模板_wrapper.c。 + +### 硬件移植的一般过程 + +HAL接口是通过s-function实现与飞控硬件接口的调用。只需要将template目录复制到新目录下,并在*_wrapper.c模板文件的基础上根据硬件配置实现其具体功能,最终在飞控代码生成中指向新目录(addpath),即可生成包含此飞控系统接口代码的完整程序。 + +### wrapper函数需要实现的功能 + +`*_Start_wrapper()`函数用于设备初始化、可配置参数的注册与绑定。 + +`*_Outputs_wrapper()`函数一般用于程序正常运行时的调用。还可以细分为三种被调用情况: + + 调用情况 | 说明 + ------- | ---------------------- + 周期调用 | 通常情况,按固定周期被调用。常用于输入与输出。 +在Function-Call中被调用 | 通常是被StateFlow状态机调用。常用于事件处理(Event)。 +在initialize function中被调用 | 在所有`*_Start_wrapper()`后,也就是初始完成后,正式启动前被调用,因此也可称为后初始化(post_init)。
这种用法的特殊在于此时可以保证所有其他模块都初始化可用,另一个好处是该调用有输入inputs,而`*_Start_wrapper()`没有输入只有参数传入。 + +生成代码的执行流程见下图。 + +```mermaid +graph TD; + inits --> period-call + starts -.-> |call| call_start["*_Start_wrapper()"] + inits -.-> |call| call_outputs["*_Outputs_wrapper()"] + period-call -.-> |call| call_outputs + func-call -.-> |call| call_outputs + subgraph "initialize" + starts["init"] --> inits["post-init"] + end + + subgraph "step" + func-call + period-call --> period-call + end +``` + +## hal_api_uart库 + +hal_api_uart.slx实现了对串行流的抽象操作,分别是初始化配置(hal_uart_init)、读(hal_uart_in)与写(hal_uart_out)。 + +模块 | 功能 | 备注 +---- | ---- | ----- +hal_uart_init | 初始化配置 | 可以配置串口波特率、读写FIFO长度等参数。
如果某FIFO长度为零,则表示这不是一个双向读写设备。
其内部是一个initialize function。 也就是说它的Outputs函数只会在整个系统Start后Step前调用。 +hal_uart_in | 读取串行流 | 从串行流中尽量读取数据到Data缓存,读取长度len不会超出Data的长度。
如果出错,ErrorCode返回非零值。 +hal_uart_out | 输出到串行流 | Data是输出的缓存,可用长度为len。当seq号变化时表示有新数据需要从缓存输出。 + +模块依赖关系如下图。 + +```mermaid + graph LR; + hal_api_uart(hal_api_uart.slx) --> hal_uart_init + hal_api_uart(hal_api_uart.slx) --> hal_uart_in + hal_api_uart(hal_api_uart.slx) --> hal_uart_out + hal_uart_init -- startup --> hal_uart_init_Start_wrapper --> initialize_uart_device + hal_uart_init -- initialize --> hal_uart_init_Outputs_wrapper + hal_uart_in -- startup --> hal_uart_in_Start_wrapper --> initialize_uart_device + hal_uart_in -- step --> hal_uart_in_Outputs_wrapper + hal_uart_out -- startup --> hal_uart_out_Start_wrapper --> initialize_uart_device + hal_uart_out -- step --> hal_uart_out_Outputs_wrapper +``` + +### hal_uart_init模块实现 + +hal_uart_init模块的功能在hal_uart_init_wrapper.c中实现。其中,hal_uart_init_Start_wrapper将在初始化时被调用,它会去调用一个单例函数initialize_uart_device。无论被调用几次(每个hal_api_uart库中模块初始化(start)都会去调用它),单例函数initialize_uart_device只应当初始化对应UART设备一次。 + +initialize_uart_device函数应该对设备初始化,但无需配置(进行默认配置也可以)。 +它还应当实现对设备属性的绑定注册功能(利用slparm的param_mgr.h绑定设置/读取波特率、字长、中止位、校验位等的函数),这样稍后就可以让地面站配置其波特率;或者从NVRAM中读取参数配置其波特率等属性。 + +hal_uart_init_wrapper.c中的hal_uart_init_Outputs_wrapper函数被设计成在所有模块start后被初始化调用,这个函数应用于实现配置UART设备的默认波特率及FIFO空间大小。 + +### hal_uart_in模块实现 + +hal_uart_in模块的功能应在hal_uart_in_wrapper.c中实现。 +hal_uart_in_Start_wrapper用于调用单例初始化函数initialize_uart_device。 + +hal_uart_in_Outputs_wrapper用于实现从uart_id设备中读取数据到Data中,并返回本次读取的数据长度Data_len(读取数据长度最多不超过y_width)。 +当未被正常初始化或其他读取异常状态时,返回非零值。 + +### hal_uart_out模块实现 + +hal_uart_out模块的功能应在hal_uart_out_wrapper.c中实现。 +hal_uart_out_Start_wrapper用于调用单例初始化函数initialize_uart_device。 + +hal_uart_out_Outputs_wrapper用于实现uart_id设备对应seq变化时,写入长度len(不超过u_width)数据到buff中。 +当未被正常初始化或其他写入异常状态时,返回非零值。 + +## hal_api_nvram库 + +hal_api_nvram.slx实现了对非易失存储设备(如FRAM/EEPRM/FLASH/SDMMC设备)的抽象操作,分别是读(hal_nvram_read)、写(hal_nvram_write)与取消(hal_nvram_cancel)。三模块都最终封装为function-call形式,因此应当使用StateFlow对其进行调用操作。 + +模块 | 功能 | 备注 +---- | ---- | ----- +hal_nvram_read | 从nvram_id设备的StartAddress地址开始读取Length到Buffer中 | ErrorCode返回负值为出错,返回正值为还有多少字节未读,返回零表示完成。 +hal_nvram_write | 从nvram_id设备的StartAddress地址开始从Buffer写Length字节 | ErrorCode返回负值为出错,返回正值为还有多少字节未写,返回零表示完成。 +hal_nvram_cancel | 取消nvram_id设备的StartAddress地址开始的读写任务 | 必须nvram_id与StartAddress都匹配的作业才能取消 + +模块依赖关系如下图。 + +```mermaid + graph LR; + hal_api_nvram(hal_api_nvram.slx) --> hal_nvram_read + hal_api_nvram --> hal_nvram_write + hal_api_nvram --> hal_nvram_cancel + hal_nvram_read -- startup --> hal_nvram_read_Start_wrapper --> initialize_nvram_device + hal_nvram_read -- call --> hal_nvram_read_Outputs_wrapper + hal_nvram_write -- startup --> hal_nvram_write_in_Start_wrapper --> initialize_nvram_device + hal_nvram_write -- call --> hal_nvram_write_Outputs_wrapper + hal_nvram_cancel -- startup --> hal_nvram_cancel_Start_wrapper --> initialize_nvram_device + hal_nvram_cancel -- call --> hal_nvram_cancel_Outputs_wrapper +``` + +### 接口设计思路 + +其中initialize_nvram_device是单例函数,实现对nvram_id号的设备进行单次的初始化。 +nvram_id号从0开始,可以表示单个FRAM/EEPRM/FLASH/SDMMC设备。 +但更本质上,nvram_id号是某对NVRAM设备独立操作进程的序号。比如可以同时2个进程对SDMMC进行读写,那么即使只有1个SDMMC设备,也应该有两个nvram_id号,用于独立处理。 + +这组接口在实现上需要有一定技巧。 +一般NVRAM的读写操作都需要一定时间,但该接口要求即时返回,不能长时间占用。 +因此设计上应当是每个nvram_id号与一个NVRAM处理进程相关联,接口只用于触发读写任务、监控当前任务状态。 +当前任务可以用nvram_id与start_address的组合进行标识。如果读写操作未完成,而新调用的start_address改变了,则应当认为返回-2(Ocuppied)拒绝新读写调用。 + +ErrorCode返回值 | 含义 | NVRAM处理进程状态 + ------------- | ---- | ------------- + -3 | Canncelled | 当前任务已经被取消,可以开始下次任务 + -2 | Ocuppied | 其他任务正在执行中,无法开始任务 + -1 | Genral Err | 一般性错误,未完成初始化等 + 0 | Completed | 当前任务已完成,可以开始下次任务 + $`>0`$ | Working | 当前任务正在传输,请等待 + +此外,NVRAM设备一般是按块(页)读写的,而本接口设计上为了使用方便属于按段读写。因此应当在处理进程中设计缓存Buffer/Cache。 +当写操作时,如果在同一块内写,则先写入块buffer中,到超时或开始写其他块时才真正开始写入。 +当读操作时,先读取整个块到cache中,如果读取都在cache中则命中,否则再读对应块。 + +### hal_nvram_read模块实现 + +hal_nvram_read模块的功能应在hal_nvram_read_wrapper.c中实现。 +hal_nvram_read_Start_wrapper用于调用单例初始化函数initialize_nvram_device。 + +hal_nvram_read_Outputs_wrapper用于实现启动/监测nvram_id进程从start address开始读取长度len(不超过y_width)数据到buffer中。返回值是当前未读入字节数,零表示写入完成,小于零值表示错误。 + +### hal_nvram_write模块实现 + +hal_nvram_write模块的功能应在hal_nvram_write_wrapper.c中实现。 +hal_nvram_write_Start_wrapper用于调用单例初始化函数initialize_nvram_device。 + +hal_nvram_write_Outputs_wrapper用于实现启动/监测nvram_id进程从start address开始从buffer写长度len(不超过u_width)数据。返回值是当前未写入字节数,零表示写入完成,小于零值表示错误。 + +### hal_nvram_cancel模块实现 + +hal_nvram_cancel模块的功能应在hal_nvram_cancel_wrapper.c中实现。 +hal_nvram_cancel_Start_wrapper用于调用单例初始化函数initialize_nvram_device。 + +hal_nvram_cancel_Outputs_wrapper用于实现取消nvram_id进程从start address开始的读写作业。返回值是零表示完成取消,非零值表示错误。 + +## hal_api_ctrls库 + +hal_api_ctrls.slx实现了对输入输出设备的抽象操作。 +模块简介如下: + +模块 | 功能 | 备注 +----------- | -------- | ----------------- +hal_pwm_out | PWM输出 | 单位us,类型uint16 +hal_pwm_in | PWM输入 | 同上 +hal_do | 离散量输出 | 类型bool +hal_di | 离散量输入 | 同上 +hal_ao | 离散量输出 | 单位V,类型single +hal_ai | 离散量输入 | 同上 +hal_led_set | 三色LED输出 | 类型Bus: LedColorMsg, rgb:0-255 uint8 +hal_sbus_in | Sbus接收机输入 | 类型Bus: SbusInMsg + +模块依赖关系如下图。 + +```mermaid + graph LR; + hal_api_ctrls(hal_api_ctrls.slx) --> hal_pwm_out + hal_api_ctrls --> hal_pwm_in + hal_api_ctrls --> hal_do + hal_api_ctrls --> hal_di + hal_api_ctrls --> hal_ao + hal_api_ctrls --> hal_ai + hal_api_ctrls --> hal_led_set + hal_api_ctrls --> hal_sbus_in + hal_pwm_out --> hal_pwm_out_wrapper[hal_pwm_out_wrapper.c] + hal_pwm_in --> hal_pwm_in_wrapper[hal_pwm_in_wrapper.c] + hal_do --> hal_do_wrapper[hal_do_wrapper.c] + hal_di --> hal_di_wrapper[hal_di_wrapper.c] + hal_ao --> hal_ao_wrapper[hal_ao_wrapper.c] + hal_ai --> hal_ai_wrapper[hal_ai_wrapper.c] + hal_led_set --> hal_led_set_wrapper[hal_led_set_wrapper.c] + hal_sbus_in --> hal_sbus_in_wrapper[hal_sbus_in_wrapper.c] + hal_led_set_wrapper --> hal_led_set_bus[hal_led_set_bus.h] + hal_sbus_in_wrapper --> hal_sbus_in_bus[hal_sbus_in_bus.h] + hal_led_set_bus --> hal_api_busdef[hal_api_busdef.mat] + hal_led_set_bus --> hal_api_busdef +``` + +### hal_api_ctrls接口设计思路 + +对于PWM、DIO、AIO这些输入输出接口,不管是几个设备拼成的,每一类的通道需要统一编址。 +如PWM输出可能一个时钟控制4个通道,另一个时钟控制2个通道,我们无需给每组编制一个设备号,而是统一从零开始编号(0、1、2、3、4、5、……)。 +而访问这些通道的时候,可以从start_idx开始连续读写。 +读写的长度取决于给定的输入输出缓存空间width与真实通道的长度(两者取短)。 +如果真实能够的读写的长度小于缓存空间,应返回非零的ErrorCode。 + +### hal_pwm_out模块实现 + +hal_pwm_out模块的功能应在hal_pwm_out_wrapper.c中实现。 +hal_pwm_out_Start_wrapper用于调用单例初始化函数pwm_out_init。 +pwm_out_init可以初始化PWM输出设置并绑定PWM周期设定与读取参数。 + +hal_pwm_out_Outputs_wrapper用于实现输出PWM指令到编号从start_idx到start_idx+u_width-1的通道。 +ErrorCode返回值非零表示错误,一般是对应通道不存在。 + +### hal_pwm_in模块实现 + +hal_pwm_in模块的功能应在hal_pwm_in_wrapper.c中实现。 +hal_pwm_in_Start_wrapper用于调用单例初始化函数pwm_in_init。 +pwm_in_init可以初始化PWM输入设置并绑定相关参数。 + +hal_pwm_in_Outputs_wrapper用于实现从编号start_idx到start_idx+u_width-1的通道读取PWM输入脉宽值。 +ErrorCode返回值非零表示错误,一般是对应通道不存在。 + +### hal_do模块实现 + +hal_do模块的功能应在hal_do_wrapper.c中实现。 +hal_do_Start_wrapper用于调用单例初始化函数discrete_output_init。 +discrete_output_init可以初始化离散量输出设置并绑定相关参数。 + +hal_do_Outputs_wrapper用于实现输出离散指令到编号从start_idx到start_idx+u_width-1的数字通道。 +ErrorCode返回值非零表示错误,一般是对应通道不存在。 + +### hal_di模块实现 + +hal_di_in模块的功能应在hal_di_wrapper.c中实现。 +hal_di_Start_wrapper用于调用单例初始化函数discrete_input_init。 +discrete_input_init可以初始化离散输入设置并绑定相关参数。 + +hal_di_Outputs_wrapper用于实现从编号start_idx到start_idx+u_width-1的通道读取离散量输入值。 +ErrorCode返回值非零表示错误,一般是对应通道不存在。 + +### hal_ao模块实现 + +hal_ao模块的功能应在hal_ao_wrapper.c中实现。 +hal_ao_Start_wrapper用于调用单例初始化函数analog_output_init。 +analog_output_init可以初始化模拟量输出设置并绑定相关参数。 + +hal_ao_Outputs_wrapper用于实现输出模拟指令到编号从start_idx到start_idx+u_width-1的DAC通道。 +ErrorCode返回值非零表示错误,一般是对应通道不存在,或者超量程。 + +### hal_ai模块实现 + +hal_ai_in模块的功能应在hal_ai_wrapper.c中实现。 +hal_ai_Start_wrapper用于调用单例初始化函数analog_input_init。 +analog_input_init可以初始化ADC输入设置并绑定相关参数。 + +hal_ai_Outputs_wrapper用于实现从编号start_idx到start_idx+u_width-1的通道读取ADC采样值。 +ErrorCode返回值非零表示错误,一般是对应通道不存在。 + +### hal_sbus_in + +hal_sbus_in模块的功能应在hal_sbus_in_wrapper.c中实现。 +hal_sbus_in_Start_wrapper用于调用单例初始化函数sbus_input_init。 +sbus_input_init可以初始化sbus输入设置并绑定相关参数。 + +hal_sbus_in_Outputs_wrapper用于实现根据id编号读取sbus信号。信号包括18通道的输入(uint16)、seq循环计数(收到新包就加一)、ErrorCode返回值非零表示未收到遥控器信号(对应遥控器接收机红灯)。 + +### hal_led_set + +hal_led_set是一个function-call模块,用于StateFlow设置三色LED, +其功能应在hal_led_set_wrapper.c中实现。 +hal_led_set_Start_wrapper用于调用单例初始化函数led_set_init。 +led_set_init根据设备号id初始化相应设备并绑定参数。 + +hal_led_set_Outputs_wrapper用于根据id设置LED颜色。ErrorCode返回值非零表示错误,一般是对应设备不存在。 + +对于带pwm控制的三色LED,rgb通道取值范围在0-255;如果只是三色,则取值零与非零;如果是单色,则是any(rgb)。 + +LED长短闪烁颜色等功能可以在stateflow中实现。 + +## hal_api_sens库 + +hal_api_sens.slx实现了对传感器设备(如IMU/INS/Baro/Radar)的抽象。抽象接口有两大类,一般性接口与特化接口。其中一般性接口设计借鉴了simulink中ROS接口设计思路。 + +### hal_sen_read接口设计与实现 + +hal_sen_read模块用于根据信号名称获取值。 +信号名称可以从交互界面中的下拉列表中查找。 +而这个列表可以通过*hal_sen_popup_update.m*脚本进行更新。 +信号可以是标量或向量,但当前的限制是类型必须是single。 + +hal_sen_read模块通过hal_sen_read_wrapper.c文件实现其功能。 +hal_sen_read_Start_wrapper函数用于根据name名称建立关联信号量,并将关联信息储存在模块的pW中。 + +pW定义 | 类型 | 说明 +------ | --- | ------- +`pw[0]` | int | 是否关联好。0-未关联;1-关联。 +`pw[1]` | PROP_TYPE | 信号量类型 +`pw[2]` | int | 信号量长度。 +`pw[3]` | void* | 指向信号对应的地址指针或函数指针 +`pw[4]` | void* | 传给函数指针对应函数第二个自定义参数 + +PROP_TYPE定义如下。ARRAY表示指向地址,FUNC表示指向函数。 + + PROP_TYPE | enum序号 + --------- | -------- + PROP_ARRAY_UINT8 | 1 + PROP_ARRAY_INT8 | 2 + PROP_ARRAY_UINT16 | 3 + PROP_ARRAY_INT16 | 4 + PROP_ARRAY_UINT32 | 5 + PROP_ARRAY_INT32 | 6 + PROP_ARRAY_REAL32 | 7 + PROP_FUNC_UINT8 | 8 + PROP_FUNC_INT8 | 9 + PROP_FUNC_UINT16 | 10 + PROP_FUNC_INT16 | 11 + PROP_FUNC_UINT32 | 12 + PROP_FUNC_INT32 | 13 + PROP_FUNC_REAL32 | 14 + +`pw[3]`作为函数指针是类似`float (*get_prop_f_func_ptr)(int index, void *paramter)`形式,用于提取第index个信号分量。完整声明如下: + +``` +typedef int8_t (*get_prop_b_func_ptr)(int index, void *paramter); +typedef uint8_t (*get_prop_B_func_ptr)(int index, void *paramter); +typedef int16_t (*get_prop_h_func_ptr)(int index, void *paramter); +typedef uint16_t (*get_prop_H_func_ptr)(int index, void *paramter); +typedef int32_t (*get_prop_i_func_ptr)(int index, void *paramter); +typedef uint32_t (*get_prop_I_func_ptr)(int index, void *paramter); +typedef float (*get_prop_f_func_ptr)(int index, void *paramter); +``` + +`pw[4]`或者说是`void *paramter`是传入调用函数的自定义参数,可以表示为一个32位数字,也可以是函数、对象的指针。可类似于C++中的this指针使用。 + +此外,还可以在hal_sen_read_Start_wrapper中注册属性。 +属性与信号都是名称与数值的关联,但用法上有明显的区别: + +* 属性表示的是设备的状态,而信号是设备源源不断产生的; +* 属性是可以读可以写(设置)的,而信号量是只读的(可写的信号量是hal_api_ctrls里的输出模块); +* 属性是标量(向量则变成标量组),而信号量可以是向量; +* 属性量可以是整型或浮点类型(union),而信号量当前只能是浮点类型; + +hal_sen_read_Outputs_wrapper函数用于提取信号向量,如果hal_sen_read_Start_wrapper中关联pW成功,则一般无需改动。其实现就是依次从地址或函数调用中提取信号诸元素并返回。 + +## 测试 + +工程使用Gitlab-CI实现自动化测试。当前测试项目`test/scriptTest.m`是通过代码生成与编译`hal_template.slx`模型,检查接口设计正确性。 _______ - Copyright (C) 2020 GONG Zheng(matt@matthewgong.com) http://www.matthewgong.com/ - - + Copyright (C) 2020 GONG Zheng(matt@matthewgong.com) diff --git a/hal_api_busdef.mat b/hal_api_busdef.mat index 65c3801..0d6afb7 100644 Binary files a/hal_api_busdef.mat and b/hal_api_busdef.mat differ diff --git a/param b/param index 35ea3ae..a884588 160000 --- a/param +++ b/param @@ -1 +1 @@ -Subproject commit 35ea3ae14977a3dde7fdb8bf7b98bd94bdd2017a +Subproject commit a884588641587a119fe0ca42fdc3f962aa7ea38c