赞
踩
a) gpio_init(); //初始化全部IO口为初始状态(输入、关上拉、关复用)
b) pre_init(); //初始化时钟与RTC0并进行待机,待RTC0中断唤醒。用此方式待电源稳定
c) init1(); //初始化LED、MOTOR、TIMER、ADC、G-SENSOR等硬件。初始化TIMER、系统调度等相关服务
d) 检测是否在充电状态,如果是则关闭系统
e) bootup_check();//启动检测,点亮全部LED。如果未进行过出厂测试,则进行出厂自检
f) reset_check(); //复位检测,在这里代码只是简单输出信息。如打开了INTERNAL_DEV宏,则为内部设备,进行FLASH删除
g) init2(); //初始化看门狗,定时器服务创建,外部中断服务(gpiote_int_init())初始化,系统时间初始化,bond管理器初始化,蓝牙协议栈初始化,蓝牙服务创建,打开G-SENSOR
h) 读取闹铃、用户信息、用户目标、运行/睡眠数据
a) void L1_receive_data(ble_nus_t * p_nus, uint8_t * data, uint16_t length)
L1_receive_data()为蓝牙通信接收的入口函数,该函数为蓝牙UART服务的接收数据回调函数。
通过数据包长度字段判断是否接收完整一个包(一帧或多帧),接收完成后调用L1_crc_check函数校验CRC是否正确。如果CRC正确则调用L1_receive_response返回ACK,并调用L2_frame_resolve解释数据包。如CRC校验错误,则调用L1_receive_response返回NAC,并调试schedule_crc_error_handle(这个任务只是简单输出信息)。
b) uint32_t L2_frame_resolve(uint8_t * data, uint16_t length,RECEIVE_STATE * resolve_state)
当接收完整的数据包并且CRC校验正确,就会进入该函数中解释数据包。程序首先判断是否有手环已绑定。未绑定状态下,只可以响应绑定、工厂测试、固件升级、输出日志的命令。绑定状态下可响应全部命令。
c) uint32_t L1_send(L2_Send_Content * content)
数据的发送通过调用L1_send实现,该函数的参数为发送数据包的指针函数。如在return_alarm_list()中返回的闹钟列表命令,通过填充全局变量global_reponse_buffer来实现发送数据的链接。在L1_send()函数里再使用全局变量global_L1_header_buffer封装成完成的数据包。最后调用schedule_async_send()调度发送服务。发送部分比较复杂,后面将单独分析。
a) void send_all_data(bool is_from_cb)
该函数为上传数据的唯一接口,函数里面先启动发送数据定时服务。然后依次检测是否有数据,检测顺序为sleep in flash > sleep in ram > sleep setting in ram > sport in flash > sport in ram,如有数据则调用相应的send函数,如send_sleep_data_in_ram()。
该函数进入时判断是否为绑定状态,非绑定状态下不传数据。接着判断sending_flag是否为true,如果为true说明正在发送数据,则启动发送服务定时器,达到定时器服务中断后再重试的效果。
b) void send_data_timeout(void * val)
定时发送服务函数,该定时服务的定时时间为5000MS。里面所做的处理就是重复调用send_all_data()去发送数据。
c) void send_all_data_callback(SEND_STATUS status)
该函数为发送数据的回调函数,在低层数据发送完成后会调用该回调函数进行发送错误处理,或更新发送缓存处理。
duBand使用的定时器服务是NRF51 SDK里面提供的APP定时器服务接口,该服务使用RTC中断作为APP定时器中断,通过SWI0(app_timer_start()与app_timer_stop())中断调用LIST TIMER HANDING来设置定时器状态。
a) void timer_init()
在main()-->init1()中调用该函数初始化APP定时器,主要是设置APP定时器的链表。
b) void timers_create(void)
在main()-->init2()中调用该函数建立APP定时器,其实就是填充各APP定时器链表。
c) void RTC1_IRQHandler(void)
RTC1中断,为1ms一次的中断。在timer_init()中初始化该中断。在该中断中主要调用timer_timeouts_check()遍历APP定时器链表。
d) void SWI0_IRQHandler(void)
软件中断0。主要用于更新定时器状态,要启动、停止定时器服务时会产生此中断。
e) void timer_timeouts_check(void)
在RTC1中断中调用,主要作定时定时值自减。
f) void timer_list_handler(void)
更新定时器状态。在SWI0中断中调用。
g) uint32_t app_timer_start(...)
启动APP定时器。
h) uint32_t app_timer_stop(app_timer_id_t timer_id)
停止APP定时器。
i) void RTC0_IRQHandler(void)
RTC0中断,在启动时的初始化用作wakeup中断,程序运行过程中没有再使用。
duBand使用的任务调度器是NRF51 SDK里面提供的服务接口,该调试器与定时器服务差不多,只是不需要中断去触发/参与,任务的调试函数直接放在了main()的while(1)循环中。调度器利用的事件触发的方式。
a) void scheduler_init(void)
在main()-->init1()中调用该函数初始化APP调度器,主要是设置APP调度器数组信息。
b) void app_sched_execute(void)
在main()的while(1)循环中调用,用于判断事件缓冲(数组)中是否有要执行的任务,有则调用注册时的handler函数。
c) uint32_t app_sched_event_put(...)
调用该函数注册事件。输入的参数包含事件的处理函数。
RTC通过一个1S的定时器服务(ID为wallClockID)处理。每定时中断一次,SecondCountRTC自加1。RTC时钟最终通过函数将SecondCountRTC转换为时间值。
a) void system_clock_init(void)
在main()-->init2()调用该函数初始化RTC时钟(实际是注册1S的定时器服务)与初始化全局时钟数据。最后启动1S定时器服务,开始计算时间。
b) void update_wall_clock(void * p_context)
1S定时器服务函数。该函数中SecondCountRTC自加1。并处理动作/睡眠数据。闹钟的检测也是在这里调用。
duBand的通知包括有LED和MOTOR。分别由ID为flash_schedule_timer与motor_schedule_timer的定时器服务控制。这两个定时器服务在main()--->init2()--->timers_create()中创建。
a) void notification_start( uint8_t type, uint16_t value )
通过调用该函数启动相应通知。通过已定义的通知类型用LED与MOTOR将当前通知表现出来。
b) void notification_stop( )
通过该函数停止LED和MOTOR通知。
c) void led_schedule_timer_handle(void * context)
LED定时器服务处理函数。里面的操作也主要是通过映射表进行操作。
d) void motor_schedule_timer_handle(void * context)
MOTOR定时器服务处理函数。里面的操作也主要是通过映射表进行操作。
a) void restart_health_algorithm(void)
运动算法的由restart_health_algorithm()函数启动。最初调用此函数是在main()函数中的init2()之后。但要执行到这步,必须restore_backup_info_from_flash()的返回值要为NRF_SUCCESS。
b) uint32_t restore_backup_info_from_flash(void)
该函数先验证FLASH_PAGE_STORE_TIME地址的信息是否正确,从名字来看这里存放的应该是时间的信息。由于我的设备中未进行时间的设备,因此这个函数发现CRC错误,就直接返回ERROR。如果需要restart_health_algorithm()能被调用,也可以通过命令去设置设备的时间从而启动运动数据采集线程,注意的是要登录状态下才可以设置时间。
设置时间后,如果检测到为已绑定的设备,则会调用restart_health_algorithm()启动数据检测。打开DEBUG_LOG,可看到有相关的数据输出。quarter_steps为本周期(15分钟)的计步数,global_step_counts_today为当天的总计步数。
.
c) uint8_t start_health_algorithm(userprofile_union_t *user_profile)
启动运动检测算法,在restart_health_algorithm()中调用,该函数输入参数为用户信息,函数开始先检测用户输入信息是否合法,如不合法则赋默认值 。接着调用算法初始化函数init_health_algorithm(...)初始化Baidu算法,init_health_algorithm(...)的输入参数为采样率和用户信息。最后调用register_health_algorithm_callback(...)注册算法回调函数和启动G-SENSOR。
d) void health_algorithm_cb_implement(algorithm_event_t *event, void* user_data)
该函数为Baidu算法的回调函数(睡眠数据的回调也是它)。当发生计步事件后(非G-SENSOR触发事件),会调用此回调函数。由于关闭了SPORTS_DATA_MOCK_TEST宏,因此其实通过判断event->event_common.type去执行handle_distance_event()或handle_sleep_event(),如果为DISTANCE_EVENT则为动作事件,SLEEP_EVENT则为睡眠事件,event->event_common.type是在算法内赋值的。
e) void handle_distance_event(algorithm_event_t *event)
输入参数event包含着此次采样的步数运动模式(快走、快走、跑)等相关信息,在该函数内只需要读取出来并填充至相关全局变量中即可。另外在该函数内还作了目标完成度的判断。
duBand睡眠起始由4个连续敲击开始。敲击事件触发后调用two_Double_tap_handle()进行处理,该函数再调用switch_sleep_state()进行睡眠状态的切换。
a) void two_Double_tap_handle(void)
duBand睡眠起始由4个连续敲击开始。敲击事件触发后调用two_Double_tap_handle()进行处理,再调用switch_sleep_state()进行睡眠状态切换。
b) void switch_sleep_state( int sleep_time_by_minutes )
该函数调用set_curr_sleep_flag()改变curr_sleep_flag标志。同时设置manual_sleep_event变量。
c) static void update_wall_clock(void * p_context)
秒定时器服务。该服务在system_clock_init()中初始化并启动,每秒执行一次。在此函数内还做了一个分钟中断(通过判断全局秒数判断),分钟中断服务函数为minute_timer_handler()。
d) void minute_timer_handler(void * val)
分钟定时中断服务函数。判断一天的开始(数据清零)也是在此函数中。最后调用update_sleep_status()处理睡眠数据。
e) void update_sleep_status(void)
在该函数内使用到刚才第b步设置的curr_sleep_flag标志。如果判断到目前为非睡眠状态并且打开了FEATURE_AUTO_SLEEP宏,则系统自动判断是否需要进入睡眠模式,如需要则调用switch_sleep_state()切换。如果判断到处于睡眠状态并且打开了FEATURE_AUTO_WAKEUP宏,则会根据睡眠时长或动作量判断是否需要调用switch_sleep_state()切换睡眠状态。
f) void health_algorithm_cb_implement(algorithm_event_t *event, void* user_data)
该函数为Baidu算法的回调函数(运动数据采集的回调也是它)。当发生计步事件后(非G-SENSOR触发事件),会调用此回调函数。由于关闭了SPORTS_DATA_MOCK_TEST宏,因此其实通过判断event->event_common.type去执行handle_distance_event()或handle_sleep_event(),如果为DISTANCE_EVENT则为动作事件,SLEEP_EVENT则为睡眠事件,event->event_common.type是在算法内赋值的。
g) void handle_sleep_event(algorithm_event_t *event)
该函数主要是调用void sleep_evt_handler(algorithm_event_t *event)进一步处理。
h) void sleep_evt_handler(algorithm_event_t *event)
先填充睡眠信息等全局变量,再填充g_sleep_send_buffer数据(该数据在定时发送时会使用)。接着调用save_sleep_group_data()保存时间数据。
分析到目前为止发现在睡眠数据采集的相关代码中有两段处理。第一段(a~e)是通过连续4次敲击启动进入睡眠,第二段(f~h)是算法的回调函数计算睡眠数据。这两段处理没看出有什么联系。查看协议文档后发现有一指令为睡眠设定数据返回(CMD=0X05,KEY=0X05),该指令发送的是在连续4次敲击事件中设定sleep_events_buff数据。在send_all_data()中连同运动数据、睡眠数据会一起发送,估计APP是先判断睡眠设定数据中是否为睡眠状态,如果是则记录为睡眠,睡眠的详细数据在g_sleep_send_buffer中。如果为非睡眠状态,就算g_sleep_send_buffer数据中有数据,都不会识别为已睡眠。
再深入分析,觉得可能是因为在sleep_evt_handler()中设置了current_sleep_event.mode与current_sleep_event.starting_time_stamp变量,然后在update_sleep_status()判断这两变量的值,才进入睡眠。
打开了FEATURE_AUTO_SLEEP宏后,在update_sleep_status()函数中会自动判定用户是否进入睡眠状态。
经串口输出调试,发现在BaiDu算法的睡眠跟踪分为三种模式NONE_SLEEP、LIGHT_SLEEP和DEEP_SLEEP。当有7分钟处于不活动状态时,current_sleep_event.mode=LIGHT_SLEEP,进入轻睡眠。又再10分钟(共不活动17分钟)不活动状态后,current_sleep_event.mode=DEEP_SLEEP,进入深睡眠。
a) if( tm->hour >= 22 || tm->hour <= 4 )
先判断是否在正常睡眠的时间段内,duBand设定了默认的睡眠时间段为10PM~5AM。
b) get_current_sleep_data( &sleep_event )
接着获取sleep_event,里面包括睡眠模式(深睡、浅睡)、睡眠起始时间。
c) if( ( sleep_event.mode == DEEP_SLEEP ) && ( ((int)get_wall_clock_time_counter() - sleep_event.starting_time_stamp ) > (30*60)) ){ //30 minutes
这里判断是否为深度睡眠(DEEP_SLEEP),并且深度睡眠时间大于30分钟。如果两个条件都满足,证明开始进入睡眠模式,调用switch_sleep_state()切换为睡眠状态。
d) if( sleep_event.starting_time_stamp/60 <= sleepSetting_minutes + 30 ) return; //in 30 minutes, just skip
如果睡眠时间(sleep_event.starting_time_stamp)未足半小时,则不进行切换。
e) if(manual_sleep_events_head->length > MAX_SLEEP_EVT_LEN) return;
manual_sleep_events_head->length在switch_sleep_state()每次调用加一。如果此值大于20(MAX_SLEEP_EVT_LEN),即10小时,也不进行切换。
f) switch_sleep_state( sleep_event.starting_time_stamp/60 - 10 ); //15 minutes before.
如果上面的条件都满足后,则调用switch_sleep_state()切换睡眠状态。该函数的参数为sleepSetting_minutes的值。这里的调用表示用户在开始进入睡眠的时间的前10分钟开始睡眠,目前已睡眠的时间长度为(当前时间-sleepSetting_minutes)。
当打开FEATURE_AUTO_WAKEUP宏后,系统将启动自己睡眠唤醒功能,相关代码在update_sleep_status()函数内,该函数一分钟调用一次。
a) sleeping_time = get_wall_clock_time_counter()/60 - sleepSetting_minutes;
计算目前睡眠的时长,使用当前分钟数减去睡眠开始时的分钟数得出。
b) if( sleeping_time >= (12*60) ){ //12 hours
如果睡眠时长大于12个小时,则强制调用two_Double_tap_handle()进行睡眠唤醒。
c) }else if( sleeping_time >= (4*60) ){ //4 hours
如果睡眠时长大于4个小时,则可以根据BaiDu算法去唤醒。如果总步数(从4小时后开始计算)大于200或运动时间大于10分钟,则调用two_Double_tap_handle()唤醒。
如果睡眠时间小于4小时,只能通过敲击去唤醒睡眠。
a) start_click_algorithm()
敲击事件的初始化在main函数中调用start_click_algorithm()进行。
调用百度算法库的int init_click_algorithm(int sample_rate)进行敲击算法初始化,参数为采样率。
调用百度算法库的int register_click_algorithm_callback(click_algorithm_cb cb, void *user_data)设置事件回调函数,敲击事件发生后,会调用所设置的回调。
b) void click_algorithm_cb_implement(click_event_t *event, void* user_data)
该函数为敲击事件的回调函数。敲击事件发生后,启动m_double_tap_timer_id定时器处理服务,如果服务已经启动过(通过判断Taped_cnt是否为1),则不需要再开启。
c) void tap_timer_handler(void * val)
该函数为m_double_tap_timer_id所绑定的敲击定时器服务。在该函数中程序判断为长按还是短按,通过app_sched_event_put()将事件类型发给input_event_handle_schedule()函数。
d) void input_event_handle_schedule(void * p_event_data, uint16_t event_size)
该函数是最终响应敲击事件的处理过程。例如在手机申请绑定的过程中,如果为短按则调用bond_press_handle()进行绑定确认处理。
a) uint32_t resolve_private_bond_command(uint8_t key,const uint8_t * value ,uint16_t length)
手机先发送绑定命令进行绑定。该函数对绑定命令的操作先检测电量是否大于20%,小于20%是不能进行绑定的。接着保存手机发过来的用户ID到user_id数组中。接着启动user_action_delay_timer定时器服务。最后调用notification_start(NOTIFICATION_BONDING,0)设置LED闪烁事件。
b) static void user_action_timeout_handle(void * context)
该函数为user_action_delay_timer所绑定的敲击定时器服务。该服务在绑定/登录超时后调用,用于关闭相关LED闪烁,释放资源等。通过参数DO_BOND与DO_WAIT_BOND_COMMAND区分是绑定过程还是登录过程。该服务在调用 resolve_private_bond_command()时会关掉。
c) uint32_t bond_store_user_id(void)
绑定命令执行后,调用该函数保存用于ID。该函数是在ble_evt_dispatch()中调用的。ble_evt_dispatch()为蓝牙协议栈的事件响应回调函数。
要该函数执行,必须should_update_prv_bond_info变量要为true。只有在一种情况下should_update_prv_bond_info变量会置为true。就是在上面敲击事件所分析的 bond_press_handle()调用。
d) uint32_t check_user_id_bonded(const uint8_t* user_id, uint8_t length)
如果为登录命令,则调用该函数判断该用户ID是否已经进行过绑定。如果已进行绑定,则可以登录成功,否则要重新进行绑定。注意重新绑定时会删除存储在手环中的用户profile。
a) void gpiote_init(void)
此函数为GPIOTE初始化函数,在init2()中调用。该函数再调用nrf_gpiote_task_config()进行进一步初始化。
nrf_gpiote_task_config()为一个内联函数,编译时会展开至调用函数里。经分析此函数只是初始化了一个BUZZER任务。
b) LIS3DH_INT1和LIS3DH_INT2
经过全局搜索SENSOR中断引脚的配置宏,发现还有一个GPIOTE初始化函数。
c) void gpiote_int_init(void)
该函数进行G-SENSOR、CHARGE、BUTTON输入中断的初始化。将中断连接到相应的XXX_event_handler()中。
d) APP_BUTTON_INIT(...)
通过调用此宏函数初始化各个外部中断(G-SENSOR、CHARGE、BUTTON中断都被当成BUTTON中断处理)。函数中调用app_button_init()进行进一步初始化处理。
app_button_init()主要初始化中断的电平变化状态和使用app_gpiote_user_register()注册中断。app_gpiote_user_register()中则是将相关的中断处理服务关联到mp_users[]中。在GPIOTE_IRQHandler()中就是以mp_users为接口去调用相应中断的。
e) void GPIOTE_IRQHandler(void)
此函数为GPIOTE的中断服务函数入口处,发生外部中断后则进入此处理,可处理按键或G-SENSOR唤醒。在这里再跳转到相应的XXX_event_handler()中。
需要使用按键功能,需要在MDK项目配置中定义HAS_BUTTON宏。该宏打开后,在timers_create()中会增加创建btn_identi定时器,服务函数为btn_timeout_handler(),用于查询按键是否按下。
同时还要在板级配置的H文件中打开FEATURE_BUTTON宏,用于连接按键中断到function_button_event_handler()中。在该handler函数中启动btn_identi定时器。
a) void function_button_event_handler(uint8_t pin_no)
注册好中断好,当产生按键中断,则会进入此函数处理。该函数判断是否输出正确的键值,并开启定时器,用于判断是否长按。如果按键为短按,则不启动定时器,直接调用app_sched_event_put(&type,sizeof(PressType),input_event_handle_schedule)发送给调试器,按键类型为短按。
b) void btn_timeout_handler(void * p_context)
如果定时器服务发生中断,则说明为长按,app_sched_event_put(&type,sizeof(PressType),input_event_handle_schedule)发送给调试器,按键类型为长按。
电池电量存入在全局变量g_battery_voltage_mv中,从名字上可以看出单位为mV。电池百分比存放在全局变量percentage_batt_lvl中。
a) void battery_adc_dev_init(void)
ADC初始化,在main()-->init1()中调用,主要初始化AD转换器。
b) void battery_measure_init()
电池测量初始化(应用级),在main()-->init1()中调用,主要初始化应用层的电池电量检测相关任务。
c) void ADC_IRQHandler(void)
ADC转换完成中断,当一个ADC转换完成时进入该中断。该函数计算电量值g_battery_voltage_mv与电池百分比percentage_batt_lvl。
d) uint8_t voltage_detect_n_precharging(void)
系统启动时(在main()中)调用该函数是否需要预充电。如果检测到的电压小于MIN_BAT_MV(注意:因为duBand使用的是锂电池,所以此值设定为3600,及3.6V。我们手环使用的是锂锰电池,需要修改此值。转换为百分比的函数uint8_t cal_percentage(uint16_t volatage)也需要修改,可以使用NRF_SDK中HRS例程的)。如果检测电量低于此值,则调用power_system_off()关闭系统(不可唤醒)。
e) void bas_init(void)
该函数用于初始化BLE的电池电量服务(调用ble_bas_init(...)实现)。BLE电量服务发送的不是电压值,而是百分比。具体实现参考代码即可,因为跟蓝牙协议相关的东西,所以不深入分析这里。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。