nRF52实践:BLE下flash读写

418 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

本文介绍在 BLE 协议栈下如何读写flash。

介绍

nRF52832 有内部 Flash。官方 SDK 提供两种方式,一种是 FStorage 方式,较简单且底层,另一种是在 FStorage 基础上实现的的 FDS 方式。本文只涉及前者,因为在应用中已经足够了。

FStorage 访问 Flash 是异步的,需要设置回调函数。具备写、读、擦除等操作。本文使用自定义的函数接口对 FStorage 进行封装。

实践

经验

在 BLE 协议栈使能情况下,有可能写Flash会失败,可以加大下面的宏定义:

NRF_FSTORAGE_SD_QUEUE_SIZE
NRF_FSTORAGE_SD_MAX_RETRIES
NRF_FSTORAGE_SD_MAX_WRITE_SIZE

操作 Flash 和蓝牙协议栈工作(如扫描、广播、收发数据)可能会冲突,笔者实践发现,基本上都会冲突,具体解决方法见下文代码。

Flash常识

Flash 需要先擦除才能写成功,擦除是将0改为1,写是将1改为0。写入的内容需要4字节对齐,写地址本身也要4字节对齐。以下函数调用为例:

flash_write(flash_addr, p_str, len)

p_str为数据缓冲区指针,该针对需要4字节对齐,len为要写的数据,需为4的整数倍。

nRF52832 Flash 大小为 512K 字节,分为128页,每页大小为4K,每页分为8个块,每块大小512字节。Flash 的擦除单位为 4KB,官方提供的 API 接口,就是按页擦除的。

经测试,手上板子 flash 信息如下:

00> <info> app: ========| flash info |========
00> <info> app: erase unit:   4096 bytes
00> <info> app: program unit:   4 bytes
00> <info> app: ==============================

对外接口

为配合笔者硬件驱动库,设计如下接口:

  • flash_init
  • flash_write
  • flash_read
  • flash_erase

使用者根据不同的应用目的,直接调用上述接口即可,至于 FStorage 的实现细节,已经隐藏了。

代码

官方提供了 Flash 的操作例程,有如下几个:

examples\peripheral\flash_fds
examples\peripheral\flash_fstorage
examples\peripheral\flashwrite

笔者使用 flash_fstorage 例程进行改写。使用 FStorage 时,须使能宏定义NRF_FSTORAGE_ENABLED。实现代码如下:

#if NRF_FSTORAGE_ENABLED == 1static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt);
​
NRF_FSTORAGE_DEF(nrf_fstorage_t fstorage) =
{
    /* Set a handler for fstorage events. */
    .evt_handler = fstorage_evt_handler,
​
    /* These below are the boundaries of the flash space assigned to this instance of fstorage.
     * You must set these manually, even at runtime, before nrf_fstorage_init() is called.
     * The function nrf5_flash_end_addr_get() can be used to retrieve the last address on the
     * last page of flash available to write data. */
    .start_addr = 0x3e000,
    .end_addr   = 0x3ffff,
};
​
/**@brief   Helper function to obtain the last address on the last page of the on-chip flash that
 *          can be used to write user data.
 */
static uint32_t nrf5_flash_end_addr_get()
{
    uint32_t const bootloader_addr = BOOTLOADER_ADDRESS;
    uint32_t const page_sz         = NRF_FICR->CODEPAGESIZE;
    uint32_t const code_sz         = NRF_FICR->CODESIZE;
​
    return (bootloader_addr != 0xFFFFFFFF ?
            bootloader_addr : (code_sz * page_sz));
}
​
​
static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt)
{
    if (p_evt->result != NRF_SUCCESS)
    {
        NRF_LOG_INFO("--> Event received: ERROR while executing an fstorage operation.");
        return;
    }
​
    switch (p_evt->id)
    {
        default:
            break;
    }
}
​
​
// static void print_flash_info(nrf_fstorage_t * p_fstorage)
// {
//     NRF_LOG_INFO("========| flash info |========");
//     NRF_LOG_INFO("erase unit: \t%d bytes",      p_fstorage->p_flash_info->erase_unit);
//     NRF_LOG_INFO("program unit: \t%d bytes",    p_fstorage->p_flash_info->program_unit);
//     NRF_LOG_INFO("==============================");
// }
​
​
void wait_for_flash_ready(nrf_fstorage_t const * p_fstorage)
{
    /* While fstorage is busy, sleep and wait for an event. */
    while (nrf_fstorage_is_busy(p_fstorage))
    {
        (void) sd_app_evt_wait();
    }
}
​
int flash_init(void)
{
    ret_code_t ret = NRF_SUCCESS;
    nrf_fstorage_api_t * p_fs_api;
    p_fs_api = &nrf_fstorage_sd;
​
    ret = nrf_fstorage_init(&fstorage, p_fs_api, NULL);
    if (ret != NRF_SUCCESS)
    {
        return ret;
    }
​
    // print_flash_info(&fstorage);
​
    return ret;
}
​
static uint32_t round_up_u32(uint32_t len)
{
    if (len % sizeof(uint32_t))
    {
        return (len + sizeof(uint32_t) - (len % sizeof(uint32_t)));
    }
​
    return len;
}
​
int flash_write(uint32_t addr, uint8_t* data, uint32_t len)
{
    ret_code_t ret = NRF_SUCCESS;
    // 保证4字节对齐
    uint32_t len_w = round_up_u32(len);
    ret = nrf_fstorage_write(&fstorage, addr, data, len_w, NULL);
    if (ret != NRF_SUCCESS)
    {
        return ret;
    }
​
    wait_for_flash_ready(&fstorage);
    NRF_LOG_INFO(" Write flash Done.");
    return ret;
}
​
int flash_read(uint32_t addr, uint8_t* data, uint32_t len)
{
    return nrf_fstorage_read(&fstorage, addr, data, len);
}
​
int flash_erase(uint32_t addr, uint32_t pages_cnt)
{
    return nrf_fstorage_erase(&fstorage, addr, pages_cnt, NULL);
}
​
#endif

下面给出用法示例的主要代码:

// 连接参数等
typedef struct {
    uint16_t status; // 0xaa55 表示数值有效
    uint16_t conn_min_ms;
    uint16_t conn_max_ms;
    uint16_t conn_latency;
    uint16_t conn_sup_timeout;
    uint16_t padding;
} app_ble_param_t;
​
static uint8_t s_flash_write = 0; // 写 flash 标志
static app_ble_param_t s_ble_param = {0};
​
static void dts_data_handler(ble_dts_evt_t * p_evt)
{
    if (p_evt->type != BLE_DTS_EVT_TX_RDY)
        NRF_LOG_INFO("got dts event %d", p_evt->type);
​
    // 收到主机发来的NUS数据
    if (p_evt->type == BLE_DTS_EVT_RX_DATA)
    {
        dev_led_set(LED_3, OFF);
        // uint32_t err_code;
        const uint8_t* p_data = p_evt->params.rx_data.p_data;
        uint16_t length = p_evt->params.rx_data.length;
        NRF_LOG_INFO("Received data from BLE NUS, cnt: %d p_data[0]: 0x%x", length, p_data[0]);
        if (p_data[0] != 0xAA)
        {
            return;
        }
​
        /* 简单协议 0xaa开头
        支持的:
        aa 01 <发送次数> <延时毫秒数>  -- 适用于button3一次发送的参数
        aa 02 55 aa <最小> <最大> 00 00 <超时>  --  示例:最小最大为7.5ms,超时为4s:aa 02 55 aa 06 00 06 00 00 00 90 01
        // more...
        */
        // NRF_LOG_INFO("p_data: %x len: %d", p_data, length);
        NRF_LOG_HEXDUMP_INFO(p_data, length);
​
        app_ble_param_t ble_param = {0};
        // 直接用结构体转,发来的数据必须和结构体一一对应
        ble_param = *(app_ble_param_t*)(p_data+2);
        
        NRF_LOG_INFO("%d %d %d %d pstr: 0x%x", 
        ble_param.conn_min_ms, ble_param.conn_max_ms,
        ble_param.conn_latency, ble_param.conn_sup_timeout, &ble_param);
​
        memcpy(&s_ble_param, &ble_param, sizeof(app_ble_param_t));
        s_flash_write = 1; // 经测试,不能在回调函数中写flash 作标志
    }
}
​
int main(void)
{
    // 服务初始化...
    ....
    // 读参数
    flash_init();
​
    ret_code_t ret;
    // app_ble_param_t ble_param = {0};
    memset(&s_ble_param, '\0', sizeof(app_ble_param_t));
​
    ret = flash_read(FLASH_START, (uint8_t*)&s_ble_param, sizeof(app_ble_param_t));
​
    for (;;)
    {
        // 这是临时的方法
        // 不能在BLE事件中写flash,作标志,在主循环中做
        // 猜测:BLE事件为回调函数,此时未完成BLE事件,故无法写flash,待退出事件结束后方可
        if (s_flash_write)
        {
            NRF_LOG_INFO("write flash...");
            s_flash_write = 0;
            flash_erase(FLASH_START, 1);
            flash_write(FLASH_START, (uint8_t*)&s_ble_param, sizeof(app_ble_param_t));
        }
        idle_state_handle();
    }
}

在主函数中初始化 Flash,再读 flash,在主循环中判断写 Flash 标志 s_flash_write,有则先擦除再写。代码中的 dts_data_handler 函数为接收蓝牙主机数据的回调函数,当接收到数据时,设置标志变量 s_flash_write。

小结

关于 BLE 事件和 Flash 可能冲突的问题,SDK 中有提到,但还是测试了一下,经测试,如果在BLE事件中写flash,就会死机。因此设置标志,在主循环中再写 Flash。个人认为,BLE事件实际是回调函数,在回调函数中还未完成BLE事件,故无法写flash,须退出事件,结束后方可。

对于产品设计,可以在成功写入flash后,闪烁一下某个LED,方便观察状态。

参考

blog.csdn.net/qq_36347513…