NRF51822蓝牙服务(5)——FLASH存储数据

前言

有时候我们希望能够把蓝牙接收的数据保存下来,为以后调用和处理提供方便。所以这里我们可以尝试把数据存放在内部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的初始化程序比较常规:

  1. 申请一个块
  2. 块大小的设置
  3. 回调函数注册,返回状态机,用于判断是否一次操作完成
  4. 注册一个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里面就可以了,这里就不贴代码了。

实验验证

  • 手机连上之后,使用串口服务往蓝牙发送1234
  • 手机会notify相同的1234

总结

通过这个实验,我们就可以永久保存少量的数据在FLASH内部。

你可能感兴趣的:(BLE)