Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1377924
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: 嵌入式

2018-06-11 19:18:53

首先介绍section variables这个SDK中使用的特性:
因为协议栈中的fstorage模块使用了这个。这里介绍一下方便对fstorage模块的理解。

 

介绍section variables之前,先考虑一下下面的 对于flash管理模块的实现方式

通常的实现中,对于flash操作会封装成一个flash操作的管理模块,对于另外一些模块想使用flash,通常是通过一个注册接口来注册自己希望使用flash大小,flash管理模块内部分配一些flash给这些注册者,并且记录注册的数数量和各个注册者的信息。

 

这种实现就存在一些问题。

1 注册者的数量对于flash管理模块来说是运行时才能知道,只有代码实际执行了,flash管理模块内部才能通过变量知道注册了多少个。

那么维护多少个记录 注册者信息的数组就需要有一个合适的考虑,如果设置的是5过,如果以后又有另一个新增的模块需要使用flash,导致使用flash的模块超过了5个,那么就只能修改flash管理模块的内部代码。

 

2  flash使用者如果为了访问控制将记录其从flash管理模块中分配到的flash的控制变量定义为静态变量,则flash管理和模块和 flash使用模块都需要有一份使用 flash使用者 注册的flash信息。

Flash使用者需要通过这个信息知道自己 被分配的flash的起始和结束地址,读写操作的时候需要这些信息。

flash管理模块也需要知道 这个注册者被分配的flash起始地址和结束地址,用来判断这个使用者是否有越界行为。是否写到了其他模块使用的空间。

 

3 也是flash管理模块内部需要记录所有注册者信息的问题,创建一个多大的空间合适?如果为了充足和后期扩展创建了10个,而实际使用只用了3个,那么就有空间浪费的问题。

 

Section variables  SDK中使用变量的一种方式。Sdk中的flash存储模块flash storage就使用了该特性。

Section variables的好处就在于到底有多少个模块使用flash是在编译时就可以确定了。而不是在运行时。Section variables可以解决上面的 只有在运行时才能确定有多少个 flash模块使用者的问题。 也就避免了空间浪费以及因为分配的空间不够又需要去调整flash管理模块分配的记录大小问题。 同时也可以解决flash管理模块和flash都要维护记录或者的问题。因为使用sction variables,对于flash管理模块和 flash使用者来说操作的都是同一个变量。即使使用者使用的是静态变量,对于flash管理模块来说还是可以访问到。

 

直接以一个例子来看section variables

点击(此处)折叠或打开

  1. //定义一个含有两个 uint32_t成员的 结构体
  2. struct structtest{
  3.     uint32_t a;
  4.     uint32_t b;
  5. };

  6. //定义了一个section
  7. //这个section的起始地址通过 my_section_test$$Base 这个变量符号来获得
  8. //结束地址通过my_section_test$$Limit这个变量符号来获得
  9. extern struct structtest *my_section_test$$Base;
  10. extern void * my_section_test$$Limit;

  11. // 定义一个结构体变量,并且制定放在 my_section_test这个section中    
  12. static struct structtest test_variable __attribute__((section("my_section_test"))) __attribute__((used));

  13. // main函数,无实际作用,只是为了使用上面这些变量,方便看编译输出文件。
  14. int main(void)
  15. {
  16.     bool erase_bonds;
  17.     uint8_t test;
  18.     
  19.     test_variable.a = 3;
  20.     printf("%d",test_variable.a);
  21.     test = (size_t)&my_section_test$$Limit - (size_t)&my_section_test$$Base;
  22.     printf("%d",test);
  23. }

直接看keil编译输出的 map文件

My_section_test的大小为8字节,刚好 是放在这个section中的test_variable变量的大小。

再看下图,声明的my_section_test$$Base my_section_test$$Limit 就是 my_section_test 的起始和结束地址,差值就是section的大小,也就是 放在这个section中的test_variable的大小




下面再定义一个变量放入my_section_test这个section中。

struct structtest test_variable2 __attribute__((section("my_section_test"))) __attribute__((used));

再看编译后输出的map文件


可以看到my_section_test的大小变成了16字节,刚好是放在这个section中的两个结构体变量 test_variabletest_variable2的大小。

 

所以使用section variable的好处就是到底有多少个注册者,即放到这个区域中的变量的个数在编译时期就固定下来了。模块的管理者可以通过my_section_test$$Base my_section_test$$Limit这个两个section的起始和结束地址以及section中存放的变量类型来知晓 这个section中存放的变量的个数。

 

以上面的例子为例,通过

(size_t)&my_section_test$$Limit - (size_t)&my_section_test$$Base/sizeof(struct structtest)

即可计算出section中存放的变量的个数。

PS:所以也需要这个section中存放的变量类型都是一样的。

所以,对于flash管理这种应用来说,使用section variable就不会有空间浪费的问题了,因为这个section在编译期间就会自动根据放入其中的变量的个数来生成合适的大小空间。所以也不会存在因为定义的存放记录的buff小了,导致后续扩展时需要修改flash管理中定义的buff数组的大小问题。

 

另外每个使用flash的模块通过将控制变量放入 section中,而flash管理模块声明这个section

即通过以下方式声明,my_section_testsection名字,可以修改。

extern struct structtest *my_section_test$$Base;

extern void * my_section_test$$Limit;

那么即使 使用flash的模块 定义的变量声明成static类型的,flash管理模块也可以通过

((struct structtest*)& my_section_test$$Base + N) 的方式来访问其中的某个变量。


下面介绍flash storage的使用

使用BLE时,因为flash操作会停止CPU,可能导致协议栈运行状态出错,所以在使用BLE的情况下,操作flash不能直接通过芯片的寄存器来操作写/擦除的动作。需要使用协议栈向上开放的 BLE运行下安全操作flash的接口sd_flash_write/  ,但是协议栈提供的接口是异步的,即函数调用后不是立刻就能写或擦除,协议栈会根据底层状态安排实际的写和擦除时机, 正在操作完成后协议栈会向应用传递一个完成事件来指示操作是否成功。

flash storage模块正是对这两个api以及协议栈向上传递的flash操作的事件的封装,以提供更方便使用的flash操作接口以及操作失败时的重试。

 

这里使用flash storage模块实现一个 flash存储功能的简单例子。

例子实现手机连接设备后,发送AA+8字节数据给设备,设备则将该8字节数据写入flash中。 手机发送 一字节BB,则设备返回之前写入flash的数据给手机。

ble_app_uart例子的基础上添加示例代码

首先使用flash storage模块,则需要在main函数中添加 flash初始化函数()

点击(此处)折叠或打开

  1. int main(void)
  2. {
  3.     uint32_t err_code;
  4.     bool erase_bonds;
  5.     // Initialize.
  6.     err_code = app_timer_init();
  7.     APP_ERROR_CHECK(err_code);
  8.     uart_init();
  9.     log_init();
  10.     
  11.     fs_init();
  12.     ……..
  13.     ………
  14. }

Fs_init()初始化函数是对flash stoage模块的初始化,实际上其内部执行的就是设置所有注册要使用flash的控制变量。

所以每个需要使用flash的模块还需要主动注册。通过调用如下宏。

 

点击(此处)折叠或打开

  1. FS_REGISTER_CFG(fs_config_t fs_config) =
  2. {
  3. .callback = fs_evt_handler, // Function for event callbacks.
  4. .num_pages = NUM_PAGES,     // Number of physical flash pages required.
  5. .priority = 0xFE             // Priority for flash usage.
  6. };


PS:这个注册实际上使用的就是section variable,在前面section variable部分中有介绍。

一般只要设置 flash 操作完成后的回调函数 fs_evt_handler,需要使用的flash页数,以及优先级。 而实际注册后申请到的flash空间的起始地址和结束地址的设置一般由flash storage模块自动分配,起始就是在执行fs_init时,模块根据注册的优先级一次安排申请者申请到的flash的地址范围。

 

Flash storage提供了两个基本flash操作函数,写操作fs_store 和擦除页操作fs_erase

因为ble的存在flash操作实际是异步的,另外flash的写操作并不是直接就能写,如果flash中本来有数据则需要先擦除后才能写。

所以实现时,收到手机发过来的数据后,执行写前需要先执行擦除操作,协议栈实际执行操作完成后会上传一个事件给应用,flash storage模块会将这个事件传递给注册时设置的回调函数fs_evt_handler,回调函数中判断到是擦除完成则再实行写操作。

 

所以实现如下,在main.c中添加如下代码。


 

点击(此处)折叠或打开

  1. // 定义用来存放ble发送过来的8字节数据,随后将该buff中的数据写入flash中
  2. // flash操作是以字为单位的,所以这里定义的是uint32_t 而不是 uint8_t
  3. static uint32_t data_buff[2];

  4. //注册使用flash
  5. FS_REGISTER_CFG(fs_config_t fs_config) =
  6. {
  7.     .callback = fs_evt_handler, // flash操作完成后的回调函数
  8.     .num_pages = 1,          // 这里用1页flash就可以了
  9.     .priority = 0xFE // 优先级越高,则初始化时自动分配flash存储范围时,分                                               //配的地址越高
  10.     // .p_start_addr;          这里不设置起始地址和结束地址,fs_init初始化时会对所有注                                           //册的变量根据其优先级
  11.     //    .p_end_addr;         自动设置其地址范围
  12. };

  13. // flash操作回调函数,flash擦除完成后执行实际写入操作
  14. // 写入操错以 字位单位。将data_buff中的8字节数据写入flash
  15. static void fs_evt_handler(fs_evt_t const * const evt, fs_ret_t result)
  16. {
  17.     if ( FS_EVT_STORE == evt->id && FS_SUCCESS == result )
  18.     {
  19.         printf("flash store done\r\n");
  20.     }
  21.     else if ( FS_EVT_ERASE == evt->id && FS_SUCCESS == result )
  22.     {
  23.         printf("flash erase done\r\n");
  24.         fs_store(&fs_config, fs_config.p_start_addr, data_buff, 2, NULL);
  25.     }
  26. }


  27. // flash写如操作之前都是需要写进行擦除操作,所以执行更新flash数据时,先执行擦除操作。
  28. // 等收到系统返回的 擦除完成事件后,再进行flash写入操作。
  29. void my_fs_update(uint8_t *pdata, uint16_t len )
  30. {
  31.     fs_ret_t ret;
  32.     
  33.     len = len>8?8:len;
  34.     memcpy( (uint8_t*)data_buff, pdata, len);
  35.     ret = fs_erase(&fs_config, fs_config.p_start_addr, 1, NULL);
  36.     if ( FS_SUCCESS != ret )
  37.     {
  38.         printf("fs erase err\r\n");
  39.     }
  40. }

  41. // 读出flash中的前8字节数据
  42. void my_fs_read(uint8_t *pbuff, uint16_t len)
  43. {
  44.     uint8_t *psrc;
  45.     
  46.     len = len>8?8:len;
  47.     psrc = (uint8_t *)fs_config.p_start_addr;
  48.     memcpy(pbuff, psrc, len);
  49.     
  50. }


以上为flash相关的实现,再来修改工程中对接收ble数据后的处理函数


点击(此处)折叠或打开

  1. //如果收到 AA开头则写入8字节数据到flash
  2. //BB开头则读出之前写入的8字节数据并返回给手机
  3. static void nus_data_handler(ble_nus_t * p_nus, uint8_t * p_data, uint16_t length)
  4. {
  5.     uint8_t flag;
  6.     uint8_t temp_buff[8];

  7.     flag = p_data[0];
  8.     if ( 0xAA == flag )
  9.     {
  10.         printf("write ble data\r\n");
  11.         my_fs_update(p_data+1, length-1);
  12.     }
  13.     else if( 0xBB == flag )
  14.     {
  15.         printf("read flash data\r\n");
  16.         my_fs_read(temp_buff, 8);
  17.         ble_nus_string_send(&m_nus, temp_buff, 8);
  18.     }
  19. }


另外flash操作中需要处理协议栈返回的关于flash完成状态的事件,所以还要在原工程中添加一些处理。在协议栈初始化的最后注册系统事件处理函数。

static void ble_stack_init(void)

{   

    ………………

    ………………

    // Enable BLE stack.

    err_code = softdevice_enable(&ram_start);

    APP_ERROR_CHECK(err_code);

 

    // Subscribe for BLE events.

    err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);

    APP_ERROR_CHECK(err_code);

   

    err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);

    APP_ERROR_CHECK(err_code);

}

系统事件处理函数的实现很简单,就是调用flash storage模块对系统事件的处理函数

static void sys_evt_dispatch(uint32_t sys_evt)

{

    fs_sys_event_handler(sys_evt);

}

Fs_sys_event_handler中会根据flash操作返回的状态执行不同的操纵,比如操作失败会重试,操作成功则执行队列中下一个flash操作。



阅读(5523) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~