Nordic的sdk中,使用app_scheduler执行蓝牙回调中的异步任务

        刚接触的时候,碰到了一个问题,就是我们的某个蓝牙接口允许用户写入一些配置,然后我会将这些配置写到flash中保存起来。很简单的操作结果出错了,fds操作没有任何回调,问题在哪儿呢?

       我以为是fds的操作有问题,写了测试代码,结果测试代码跑的好好的,有回调!问题很明显,必然是执行的上下文环境有问题。

        前边研究fds的源码中有提到,使用fds,初始化操作的时候,要注册一个回调:(void)fds_register(fds_evt_handler);这个注册回调的函数(fds_init)是在放在main函数中执行的,和main函数跑在同一个线程中。意思就是,fds的任何操作,其操作结果最终都会发送到main函数所在的线程。详细点就是,main线程注册的回调,main线程发起的fds操作,fds发起异步线程来执行真实的flash操作,操作结果再发送回main线程。 

        当fds的操作放在ble事件的回调中的时候,情况不一样了。我没有在详细的文档中发现nordic有提到其ble中断事件,其回调是执行在异步线程中的,但是确实是这样。所以当我们在蓝牙profile的写回调中执行fds操作的时候,是这么一个过程:main线程注册的回调,异步线程A(蓝牙profile写操作回调)发起fds操作,fds再发起异步线程B来执行真实的操作,那么B操作完了,结果应该发给谁呢?这里边具体的细节我后续会在fds的源码中再解读,这里的话:结果是没有发给main线程,注册的回调收不到这个消息。

        为了解决这个问题,我们应该引入APP_SCHEDULER模块。这是一个调度模块,其主要作用是将某些不适合当前环境(比如中断)的操作发送到主线程中去执行。

        来看看怎么使用,惯例初始化操作:

/**@brief Function for the Event Scheduler initialization.
 */
static void scheduler_init(void)
{
    APP_SCHED_INIT(MAX_EVT_SIZE(APP_TIMER_SCHED_EVENT_DATA_SIZE,MAX_EVT_SIZE(PERIPHERAL_MAX_EVT_SIZE,CENTRAL_MAX_EVT_SIZE)), SCHED_QUEUE_SIZE);
}

        这里需要提供一个最大的事件长度,方便分配空间。需要注意的是,app_timer也引用了app_scheduler,这里计算最大的事件长度,也需要包含进来。我们的应用中,既使用到了蓝牙主机模式,也使用到了蓝牙从机模式,所以这里两个都要看。

        然后,在main loop中,需要检查是否有调度任务:

// Enter main loop.
	
	for (;;)
    {
		#if (MI_BLE_ENABLE)	
		mible_tasks_exec();		
		#endif
		app_sched_execute(); 
		idle_state_handle();		
    }

        还是很简单的,到了这里,就可以开始使用app_scheduler了。以修改温度单位为例,app端发过来需要设置的温度单位,摄氏度或者华氏度,app_scheduler将单位和真实的处理函数(将单位写到flash中保存的函数)保存起来,发送到main线程,在main loop中执行。

        来看看app_scheduler将事件放入队列的函数是怎么定义的?


/**@brief Function for scheduling an event.
 *
 * @details Puts an event into the event queue.
 *
 * @param[in]   p_event_data   Pointer to event data to be scheduled.
 * @param[in]   event_size     Size of event data to be scheduled.
 * @param[in]   handler        Event handler to receive the event.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
uint32_t app_sched_event_put(void const *              p_event_data,
                             uint16_t                  event_size,
                             app_sched_event_handler_t handler);

        这里的event data 就是事件的接口,既实际执行的是什么事件。那么首先定义一个事件接口,这里的话,就是fds写flash的函数指针和对应的参数:

typedef bool(*evt_update_unit_handler_t)(TEMP_UNITS unit);
typedef struct{
	evt_update_unit_handler_t m_handler;
	TEMP_UNITS temp_unit;
}update_unit_evt_data;

        这里的话,fds用来更新单位的函数为:bool update_unit(TEMP_UNITS unit)。evt_update_unit_handler_t 即为指向该函数的指针,然后参数是temp_unit。

        然后第三个参数,app_sched_event_handler_t,我理解为要怎么执行前边定义的事件,通常的做法是直接以temp_unit为参数执行m_handler。

static void update_unit_evt_get(void * p_event_data, uint16_t event_size)
{
    update_unit_evt_data * p_update_unit_event = (update_unit_evt_data *)p_event_data;
    APP_ERROR_CHECK_BOOL(event_size == sizeof(update_unit_evt_data));
    p_update_unit_event->m_handler(p_update_unit_event->temp_unit);
}

         这里做了一个检查,防止类型转换产生错误。

         现在,我们可以发起调度了:

uint32_t evt_schedule_update_unit(evt_update_unit_handler_t update_handler,TEMP_UNITS unit){	
	update_unit_evt_data evt_handler;
	evt_handler.m_handler=update_handler;
        evt_handler.temp_unit=unit;
	return app_sched_event_put(&evt_handler, sizeof(evt_handler), update_unit_evt_get);
}

        直接在蓝牙读写中断中执行函数 evt_schedule_update_unit即可。

        在APP_SCHED_INIT的时候,需要传入一个最大的事件长度,这里定义了多少个这样的evt,就需要计算多少个。我是这样定义的,每新增一个事件,就需要计算一次:

#define MAX_EVT_SIZE(a,b) (a>b?a:b)
#define PERIPHERAL_MAX_EVT_SIZE MAX_EVT_SIZE(sizeof(update_unit_evt_data),\
		MAX_EVT_SIZE(sizeof(update_profile_series_data),\
		MAX_EVT_SIZE(sizeof(update_profile_series_by_leftcnt_data),\
		MAX_EVT_SIZE(sizeof(update_algorithm_flag_data),\
		MAX_EVT_SIZE(sizeof(update_account_data),\
		MAX_EVT_SIZE(sizeof(update_profile_id_data),\
		sizeof(remove_detail_items_data)\
		))))))

 

 

 

 

你可能感兴趣的:(#,BLE,物联网,Nordic,app_scheduler,52832,fds,异步任务)