有时候我们希望能够把蓝牙接收的数据保存下来,为以后调用和处理提供方便。所以这里我们可以尝试把数据存放在内部FLASH里面,因为FLASH掉电不易失,是一个很好的存储方式。
NRF51822官方BLE协议栈实际上是提供了FLASH存储库函数,采用这些库函数,可以很方便的在工程里面进行FLASH的相关操作,这样就避免了我们重复造轮子了。
NRF51822官方在SDK例程里面提供了两个文件pstorage.c和pstorage.h(目录:工程目录\components\drivers_nrf\pstorage)。下面我们看下常用的几个API:
uint32_t pstorage_init(void);
这个函数用于初始化协议栈下的内部FLASH的使用。
uint32_t pstorage_register(pstorage_module_param_t *p_module_param, pstorage_handle_t *p_block_id);
这个函数用于注册永久存储接口功能。
参数:p_module_param 模块的注册参数
参数:p_block_id 注册成功时标识内存块的id
uint32_t pstorage_block_identifier_get(pstorage_handle_t * p_base_id,
pstorage_size_t block_num,
pstorage_handle_t * p_block_id);
这个函数用来获得你要操作的block_num(申请的block数量)的地址。
参数:p_base_id 在注册时收到的基内存块的ID
参数:block_num 内存块编号,第一个内存块编号为0
参数:p_block_id API成功申请到的内存块编号标识符
uint32_t pstorage_store(pstorage_handle_t * p_dest,
uint8_t * p_src,
pstorage_size_t size,
pstorage_size_t offset);
这个函数用于在指定位置写入存储相应大小的数据。
uint32_t pstorage_update(pstorage_handle_t * p_dest,
uint8_t * p_src,
pstorage_size_t size,
pstorage_size_t offset);
这个函数用于在指定位置更新存储相应大小的数据。
uint32_t pstorage_load(uint8_t * p_dest,
pstorage_handle_t * p_src,
pstorage_size_t size,
pstorage_size_t offset);
这个函数用于在指定位置读取存储相应大小的数据。
参数:p_dest 更新数据的目的地址
参数:p_src 数据存储源地址
参数:size 数据存储字节大小
参数:offset 写块的偏移量
uint32_t pstorage_clear(pstorage_handle_t * p_base_id, pstorage_size_t size);
这个函数用于清除存储的数据。
注意:FLASH处理函数store或者update的是分两次操作的,首先赋值原FLASH数据到交换区。然后,擦除之前放数据的page,之后再将交换区需要写入数据写入指定区域。所以这个过程需要时间。未在上次操作完成就又使用了buff,会导致前面一次的FLASH写操作出问题。因此我们在FLASH操作过程中,如果需要多次操作写入的时候,先等待上次的操作完成才能再使用缓冲。所以在编写程序时,我们需要加入一个标志位FLAG,标志上次操作已经完成,避免发生数据阻塞。
应用层目标是实现初始化FLASH、然后把收到的数据存储起来,然后在回调函数中将数据读出来。
void flash_init(void)
{
uint32_t err_code;
pstorage_module_param_t param;
err_code = pstorage_init(); //初始化flash
APP_ERROR_CHECK(err_code);
param.block_size = 512; //申请控制512个字节
param.block_count = 1; //申请一个block
param.cb = flash_callback; //注册回调函数
err_code =pstorage_register( ¶m, &m_data_num_handle ); //注册flash
APP_ERROR_CHECK(err_code);
}
FLASH的初始化程序比较常规:
- 申请一个块
- 块大小的设置
- 回调函数注册,返回状态机,用于判断是否一次操作完成
- 注册一个FLASH的操作ID
问:那么这里回调函数返回的状态机是什么呢?
答:
#define PSTORAGE_STORE_OP_CODE 0x01 /**< Store Operation type. */ #define PSTORAGE_LOAD_OP_CODE 0x02 /**< Load Operation type. */ #define PSTORAGE_CLEAR_OP_CODE 0x03 /**< Clear Operation type. */ #define PSTORAGE_UPDATE_OP_CODE 0x04 /**< Update Operation type. */
这里定义的状态机可以帮助我们判断出进行的是什么FLASH操作,当然如果你不需要判断状态,那也可不适用switch。
在协议栈中当FLASH操作完成后,需要对sys_evt事件进行处理,协议栈底层会上抛给应用层相应的sys_evt事件(类似sd会上抛给应用层BLE的事件)。同时因为pstorage的实现是基于状态机的。比如我们上面使用的update,它的实现是分步做的,先擦除交换区,然后将数据写到交换区,然后修改再写回。每个时刻下一步要做的事都由当前的操作返回后的状态机决定。所以pstorage需要获得sys_evt(FLASH的操作返回),然后进行下一步处理。
/**@brief Function for dispatching a system event to interested modules.
*
* @details This function is called from the System event interrupt handler after a system
* event has been received.
*
* @param[in] sys_evt System stack event.
*/
static void sys_evt_dispatch(uint32_t sys_evt)
{
pstorage_sys_event_handler(sys_evt);
}
/**@brief Function for the S110 SoftDevice initialization.
*
* @details This function initializes the S110 SoftDevice and the BLE event interrupt.
*/
static void ble_stack_init(void)
{
uint32_t err_code;
// Initialize SoftDevice.
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL);
// Enable BLE stack.
ble_enable_params_t ble_enable_params;
memset(&ble_enable_params, 0, sizeof(ble_enable_params));
#if (defined(S130) || defined(S132))
ble_enable_params.gatts_enable_params.attr_tab_size = BLE_GATTS_ATTR_TAB_SIZE_DEFAULT;
#endif
ble_enable_params.gatts_enable_params.service_changed = IS_SRVC_CHANGED_CHARACT_PRESENT;
err_code = sd_ble_enable(&ble_enable_params);
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);
}
接下来,既然我们要在收到数据时候存储。那么我们就在数据接收函数修改(这里在串口例程的接收处理函数中修改):
static void on_write(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)
{
ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
if (
(p_evt_write->handle == p_nus->rx_handles.cccd_handle)
&&
(p_evt_write->len == 2)
)
{
if (ble_srv_is_notification_enabled(p_evt_write->data))
{
p_nus->is_notification_enabled = true;
}
else
{
p_nus->is_notification_enabled = false;
}
}
else if (
(p_evt_write->handle == p_nus->tx_handles.value_handle)
&&
(p_nus->data_handler != NULL)
)
{
// p_nus->data_handler(p_nus, p_evt_write->data, p_evt_write->len);
pstorage_handle_t dest_block_id;
uint8_t len = p_evt_write->len > 8?8:p_evt_write->len;
memcpy(my_buff,p_evt_write->data,len);
pstorage_block_identifier_get(&g_block_id, 0, &dest_block_id);//获取要操作的block_num(申请的block数量)的地址
pstorage_update(&dest_block_id, my_buff, 8, 0);//更新手机接收的数据
}
else
{
// Do Nothing. This event is not relevant for this service.
}
}
读出数据可以直接调用API:
uint32_t pstorage_load(uint8_t * p_dest,
pstorage_handle_t * p_src,
pstorage_size_t size,
pstorage_size_t offset);
最后,把初始化函数加到main里面就可以了,这里就不贴代码了。
通过这个实验,我们就可以永久保存少量的数据在FLASH内部。