12 Variable Unified Management

一、Table of content (TOC)

简单的类比,TOC是一个存储了集团所有客户电话号码的号码薄,不同的分公司使用统一的注册机制,将客户电话写到该电话簿上,集团商务部门可以以一定频率通过遍历号码薄,与所有客户逐个通信,为客户推送信息或接受客户反馈.

在该工程中TOC是一张烧录在flash连续空间上的地址表,地址表内存储了来自不同源文件需要监控的变量名字、类型和地址(组成一个结构体).Crazyflie有两个TOC,PARAM 和 LOG,用于承载不同的功能.上位机通过获取TOC,获取全部已经注册的变量(名字、类型和地址),使用CRTP协议通过该变量地址实时查看或修改对应变量值.

The aim of the logging and parameter framework is to easily be able to log data from the Crazyflie and to set variables during runtime. Logging and parameter frameworks

二、 PARAM_GROUP

  1. 把变量加入PARAM_GROUP,方便从外部进行修改(支持设置读写权限),大大加速调试过程(该机制仅写到ram,重启后变成默认值).

  2. 支持使用{组名.变量名}访问变量:if you would like to set the effect variable in the ring group it's accessed using ring.effect.

  3. 可以在任意时刻访问和修改变量 The reading or writing of a parameter can be done at any time once you are connected to the Crazyflie.

  4. 读写没有线程安全保护:There's no thread protection on reading/writing. Since the architecture is 32bit and the largest parameter you can have is 32bit it's safe to write one variable. But if you write a group of variables that should be used together (like PID parameters) you might end up in trouble.

  5. 在访问运行期变化的变量时,推荐使用LOG_GROUP:Only use the parameter framework to read variables that are set during start-up. If variables change during runtime then use the logging framework.

1. 宏定义

/* Macros */
#define PARAM_ADD(TYPE, NAME, ADDRESS) \
   { .type = TYPE, .name = #NAME, .address = (void*)(ADDRESS), },

#define PARAM_ADD_GROUP(TYPE, NAME, ADDRESS) \
   { \
  .type = TYPE, .name = #NAME, .address = (void*)(ADDRESS), },

#define PARAM_GROUP_START(NAME)  \
  static const struct param_s __params_##NAME[] __attribute__((section(".param." #NAME), used)) = { \
  PARAM_ADD_GROUP(PARAM_GROUP | PARAM_START, NAME, 0x0)

#define PARAM_GROUP_STOP(NAME) \
  PARAM_ADD_GROUP(PARAM_GROUP | PARAM_STOP, stop_##NAME, 0x0) \
  };

2. 源文件中,将全局变量添加到PARAM TOC的方法

PARAM_GROUP_START(stabilizer)
PARAM_ADD(PARAM_UINT8, estimator, &estimatorType)
PARAM_ADD(PARAM_UINT8, controller, &controllerType)
PARAM_GROUP_STOP(stabilizer)

按照宏定义展开:

static const struct param_s __params_stabilizer[] __attribute__((section(".param.stabilizer"), used)) = {
   { .type = PARAM_GROUP | PARAM_START, .name = “stabilizer”, .address = (void*)(0x0), },
   { .type = PARAM_UINT8, .name = “estimator”, .address = (void*)(&estimatorType), },
   { .type = PARAM_UINT8, .name = “controller”, .address = (void*)(&controllerType), },
   {.type = PARAM_GROUP | PARAM_STOP, .name =“stop_stabilizer”, .address = (void*)(0x0), }, 
};

相当于创建了一个结构体(param_s)数组,该数组包含4个成员,其中第1个和第4个用于标记该组变量的头和尾,每个结构体有三个成员(type、name、address)

使用__attribute__((section(".param.stabilizer"), used)) 将该数组放在指定名为".param.stabilizer"的段.

3. 把源文件".param."##NAME 段映射到地址连续区域

3.1 esp_idf链接脚本生成器

esp_idf从v3.3开始,支持在component层级控制最后的链接阶段,使用Linker Script Generation机制,在component里添加一个xx.lf文件.

在编译系统中使用xx.lf

COMPONENT_ADD_LDFRAGMENTS += "xx.lf"

按照规定的语法填写内容:

\\示例:
[sections:text]
    .text+
    .literal+

[sections:iram]
    .iram1+

[scheme:default]
entries:
    text -> flash_text
    iram -> iram0_text

[scheme:noflash]
entries:
    text -> iram0_text

[mapping:freertos]
archive: libfreertos.a
entries:
    * (noflash)

脚本生成器将xx.lf转换成gcc任何的链接片段: 该链接片段将被添加到链接文件,形成当前工程的链接规则.

\\示例:
.iram0.text :
{
    /* Code marked as runnning out of IRAM */
    _iram_text_start = ABSOLUTE(.);

    /* Placement rules generated from the processed fragments, placed where the marker was in the template */
    *(.iram1 .iram1.*)
    *libfreertos.a:(.literal .text .literal.* .text.*)

    _iram_text_end = ABSOLUTE(.);
} > iram0_0_seg

3.2 直接修改框架的链接脚本

ESPlane2.0版本基于release/v3.3分支编写,该分支下链接脚本生成机制 1. 不支持添加一个地址的引用(添加一个入口)? 2. 不支持添加对齐指令? 因此临时跳过了使用链接脚本工具生成链接脚本,而是直接修改默认的链接脚本(不推荐,需要后期调整),(位置在/components/esp32/ld/esp32.project.ld.in)修改如下:

.flash.rodata :
{
/*省--------------------------------
*略
*已
*有
*若
*干
*行---------------------------------*/

    /* Parameters and log system datas */
    _param_start = .;
    KEEP(*(.param))
    KEEP(*(.param.*))
    _param_stop = .;
    . = ALIGN(4);
    _log_start = .;
    KEEP(*(.log))
    KEEP(*(.log.*))
    _log_stop = .;
    . = ALIGN(4);

} >drom0_0_seg

KEEP(*(.param)) KEEP(*(.param.*))可以将所有带有.param的分段,统一映射到drom0_0_seg的.flash.rodata

3.3 链接脚本修改效果

查看编译输出的.map文件可以找到.param.stabilizer,为第2部分宏定义添加的结构体数组,该数组包含4个元素,每个元素大小为12,占用大小为0x30字节

3.4 程序如何遍历该变量地址表

读取:

extern struct param_s _param_start; //添加声明,定义在3.2的ld文件中
extern struct param_s _param_stop; //添加声明,定义在3.2的ld文件中
static struct param_s * params;

params = &_param_start;//参数地址表入口
paramsLen = &_param_stop - &_param_start; //参数总个数
  for (int i=0; i<paramsLen; i++)//遍历
  {
      params[i].type;
      params[i].name;
      params[i].address; 
  }

写入:写入到ram,掉电不保存

    switch (params[id].type & PARAM_BYTES_MASK)
    {
    case PARAM_1BYTE:
        *(uint8_t*)params[id].address = *(uint8_t*)valptr;
      break;
      case PARAM_2BYTES:
        *(uint16_t*)params[id].address = *(uint16_t*)valptr;
        break;
    case PARAM_4BYTES:
        *(uint32_t*)params[id].address = *(uint32_t*)valptr;
        break;
    case PARAM_8BYTES:
        *(uint64_t*)params[id].address = *(uint64_t*)valptr;
        break;
    }

三、 LOG_GROUP

  1. 把变量加入LOG_GROUP,可以从上位机监控变量的变化:The logging framework is used to log variables from the Crazyflie at a specific interval.

  2. 上位机下载TOC并完成设置,将收到Crazyflie主动push的数据:Instead of triggering a reading of the variables at certain intervals, the framework is used to set up a logging configuration to that will push data from the Crazyflie to the host.

  3. The interval for a logging configuration is specified in 10th of milliseconds.

1. 源文件中,将全局变量添加到LOG TOC的方法

LOG_GROUP_START(stabilizer)
LOG_ADD(LOG_FLOAT, roll, &eulerRollActual)
LOG_ADD(LOG_FLOAT, pitch, &eulerPitchActual)
LOG_ADD(LOG_FLOAT, yaw, &eulerYawActual)
LOG_ADD(LOG_UINT16, thrust, &actuatorThrust)
LOG_GROUP_STOP(stabilizer)

四、 通过上位机读取TOC并监视变量

最后更新于