由于外围设备上的重置内存分配失败,我遇到了软件重置。首先让我简要介绍一下我要实现的行为:
- Central连接到外围设备(无配对或绑定)
- Central订阅“按钮状态”的通知自定义服务1的特性中央将非零值写入自定义服务1中的控制点特性。
- 在外围设备上,这将启动我在代码中称为“采样循环”(下面的代码片段)的操作。该循环实际上只是一个app_easy_计时器,在上述控制点写入零之前一直自动重置,它检查DA14531微型子板(SW2)上按钮的当前状态,并通过通知将其发送给中央。一旦用户连续3秒未按下按钮,则“非活动计时器”应触发。这将停止采样循环(即取消其计时器),并通过wakeupct启用按钮中断唤醒(在同一按钮上,SW2)。此时,设备应进入睡眠状态,仅在按下按钮或必须发生连接事件时才唤醒(我将延迟设置为最大499,因此这些延迟应间隔很长)。
- 下次用户按下按钮时,设备应唤醒,向中央发送按钮按下通知(发送此通知至关重要),然后恢复采样循环。
- 重复步骤4-6,直到将零写入控制点。
正如您所知,这与ble_app_peripheral非常相似,这是我使用的起点。
我面临的问题是有时(并非总是!)当不活动计时器即将触发时,重置内存分配失败被触发。在重置发生之前,外围设备通常需要经过几个周期在采样和休眠之间切换,但在重置触发之前发生的周期数不一致。
我在这里做了一些研究,似乎通常RESET_MEM_ALLOC_失败的原因是消息处理错误。我直接分配的唯一消息是按钮状态通知(请参阅下面的custSrvSetButtonState()),但我认为我生成这些文件的速度不会超过它们可以被消耗的速度,因为采样计时器延迟与连接间隔相同。
我使用堆日志记录来捕获重设发生时堆的状态,这就是输出:
此堆中使用的大小:496(当前)-512(最大)
在其他堆中使用的大小:404(当前)-420(最大)
在其他堆中使用的大小:592(当前)-592(最大)
在其他堆中使用的大小:0(当前)-0(最大)
其他堆中使用的大小:1180(当前)-1180(最大)
此堆中使用的大小:1336(当前)-1356(最大)
此堆中使用的大小:1180(当前)-1180(最大)
非Ret堆中使用的大小:此堆中使用的最大(当前)-1960)
在其他堆中使用的大小:0(当前)-0(最大)
将MSG_HEAP_SZ增加到6880后,仍然会使用以下堆日志输出进行重置:
>;环境堆<;<;在该堆中使用的大小:496(当前)-528(最大)其他堆中使用的大小:404(当前)-404(最大)
此堆中使用的大小:592(当前)-592(最大)
其他堆中使用的大小:0(当前)-0(最大)
其他堆中使用的大小:1180(当前)-1196(最大)
此堆中使用的大小:6856(当前)-6856(最大)
此堆中使用的非Ret堆的大小:1960(当前)-
在其他堆中使用的大小:0(当前)-0(最大)
我怀疑堆大小是否是问题所在,因为我没有做那么密集的事情(我正在发送通知)ry 30ms with a value that's a single byte). So, it's looking like a memory leak to me, but I'm not sure what I'm doing wrong with regards to memory management. Any help would be hugely appreciated! Thank you all so much.
Relevant code snippets:
/* * MACROS **************************************************************************************** */ #define CUSTS1_SAMPLE_TIMER_DELAY MS_TO_TIMERUNITS(30) #define CUSTS1_INACTIVITY_TIMEOUT MS_TO_TIMERUNITS(3000) /* * GLOBAL VARIABLE DEFINITIONS **************************************************************************************** */ ke_msg_id_t samplingTimer __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY ke_msg_id_t inactivityTimer __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY /* * FUNCTION DEFINITIONS **************************************************************************************** */ static void custSrvStartSampleLoop(void) { samplingTimer = app_easy_timer(CUSTS1_SAMPLE_TIMER_DELAY, custSrvSampleLoopTimerCallback); inactivityTimer = app_easy_timer(CUSTS1_INACTIVITY_TIMEOUT, custSrvInactivityTimerCallback); arch_puts("Sample loop started\r\n"); } static void custSrvStopSampleLoop(void) { app_easy_timer_cancel(samplingTimer); app_easy_timer_cancel(inactivityTimer); samplingTimer = EASY_TIMER_INVALID_TIMER; inactivityTimer = EASY_TIMER_INVALID_TIMER; arch_puts("Sample loop stopped\r\n"); } /** **************************************************************************************** * @brief Updates the state of the button so that the kernel can notify the subscriber. * @param[in] _pressed Whether the button is pressed. **************************************************************************************** */ static void custSrvSetButtonState(const bool _pressed) { struct custs1_val_ntf_ind_req *req = KE_MSG_ALLOC_DYN(CUSTS1_VAL_NTF_REQ, prf_get_task_from_id(TASK_ID_CUSTS1), TASK_APP, custs1_val_ntf_ind_req, DEF_SVC1_BUTTON_STATE_CHAR_LEN); if (req == NULL) { arch_puts("Failed to allocate notification\r\n"); return; } uint8_t buttonState = _pressed ? CUSTS1_BUTTON_DOWN : CUSTS1_BUTTON_UP; req->conidx = app_env->conidx; req->handle = SVC1_IDX_BUTTON_STATE_VAL; req->length = DEF_SVC1_BUTTON_STATE_CHAR_LEN; req->notification = true; memcpy(req->value, &buttonState, DEF_SVC1_BUTTON_STATE_CHAR_LEN); ke_msg_send(req); } void custSrvControlPointWriteHandler(ke_msg_id_t const msgid, struct custs1_val_write_ind const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { uint8_t val = 0; memcpy(&val, ¶m->value[0], param->length); // note(KJW): we may want to use bools instead of using the timer variables to check state if ((val != CUSTS1_CONTROL_POINT_DISABLE) && (samplingTimer == EASY_TIMER_INVALID_TIMER)) custSrvStartSampleLoop(); else if ((val == CUSTS1_CONTROL_POINT_DISABLE) && (samplingTimer != EASY_TIMER_INVALID_TIMER)) custSrvStopSampleLoop(); } void custSrvSampleLoopTimerCallback(void) { if (GPIO_GetPinStatus(BTN_PORT, BTN_PIN)) { custSrvSetButtonState(false); if (inactivityTimer == EASY_TIMER_INVALID_TIMER) inactivityTimer = app_easy_timer(CUSTS1_INACTIVITY_TIMEOUT, custSrvInactivityTimerCallback); } else { custSrvSetButtonState(true); if (inactivityTimer != EASY_TIMER_INVALID_TIMER) { app_easy_timer_cancel(inactivityTimer); inactivityTimer = EASY_TIMER_INVALID_TIMER; } } if (ke_state_get(TASK_APP) == APP_CONNECTED) { // Set it once again until Stop command is received in Control Characteristic samplingTimer = app_easy_timer(CUSTS1_SAMPLE_TIMER_DELAY, custSrvSampleLoopTimerCallback); } } void custSrvInactivityTimerCallback(void) { if (ke_state_get(TASK_APP) == APP_CONNECTED) { arch_puts("Inactivity limit reached, sleepytime...\r\n"); app_easy_timer_cancel(samplingTimer); samplingTimer = EASY_TIMER_INVALID_TIMER; wkupct_register_callback(custSrvButtonWakeupCallback); wkupct_enable_irq(WKUPCT_PIN_SELECT(BTN_PORT, BTN_PIN), WKUPCT_PIN_POLARITY(BTN_PORT, BTN_PIN, WKUPCT_PIN_POLARITY_LOW), // button is active LOW 1, // 1 event (press) 10); // debouncing time = 10 ms // TODO there are also "wkupct2" versions of the wakeupct functions, are they any better? } } void custSrvButtonWakeupCallback(void) { #if !defined (__DA14531__) if (GetBits16(SYS_STAT_REG, PER_IS_DOWN)) #endif { // not sure if this is necessary. // wkupct_quadec.h says that it is. // however, in the sample code (ble_app_sleepmode), it's only done when waking from sleep before a connection has been established. periph_init(); } arch_puts("Button wake up\r\n"); //wkupct_disable_irq(); not sure if this is necessary. only seems to be necessary if canceling irq before the callback is called. custSrvStartSampleLoop(); custSrvSetButtonState(true); }
My connection parameters:
static const struct connection_param_configuration user_connection_param_conf = { /// Connection interval minimum measured in ble double slots (1.25ms) /// use the macro MS_TO_DOUBLESLOTS to convert from milliseconds (ms) to double slots .intv_min = MS_TO_DOUBLESLOTS(30), /// Connection interval maximum measured in ble double slots (1.25ms) /// use the macro MS_TO_DOUBLESLOTS to convert from milliseconds (ms) to double slots .intv_max = MS_TO_DOUBLESLOTS(30), /// Latency measured in connection events .latency = 499, /// Supervision timeout measured in timer units (10 ms) /// use the macro MS_TO_TIMERUNITS to convert from milliseconds (ms) to timer units .time_out = MS_TO_TIMERUNITS(32000), /// Minimum Connection Event Duration measured in ble double slots (1.25ms) /// use the macro MS_TO_DOUBLESLOTS to convert from milliseconds (ms) to double slots .ce_len_min = MS_TO_DOUBLESLOTS(0), /// Maximum Connection Event Duration measured in ble double slots (1.25ms) /// use the macro MS_TO_DOUBLESLOTS to convert from milliseconds (ms) to double slots .ce_len_max = MS_TO_DOUBLESLOTS(0), };