@@ -164,6 +164,24 @@ static bool read_chg(void) {
164164 #endif
165165}
166166
167+ // Sample CHG signal at 4Hz to capture transitions to detect fault condition.
168+ #define PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME (250)
169+
170+ // After charging for a long time, we disable charging for some time. This
171+ // matches observed behavior with the LEGO Education SPIKE V3.x firmware.
172+ //
173+ // Why? Due to the way the hardware works, the hub cannot be truly turned off
174+ // while USB is plugged in. As a result, the charger is always on. For some
175+ // battery-charger pairs, this causes the battery to stop charging normally in
176+ // hardware when full as intended, automatically starting a new cycle after
177+ // some time. But in some battery-charger pairs, the charger will reach a
178+ // timeout state and not restart charging. When leaving such a combination
179+ // plugged in overnight, it will time out and not be charged in the morning,
180+ // which is not desirable. For this reason, we pause and restart charging
181+ // manually if it has been plugged in for a long time.
182+ #define PBDRV_CHARGER_MP2639A_CHARGE_TIMEOUT_MS (60 * 60 * 1000)
183+ #define PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS (30 * 1000)
184+
167185PROCESS_THREAD (pbdrv_charger_mp2639a_process , ev , data ) {
168186 PROCESS_BEGIN ();
169187
@@ -195,13 +213,11 @@ PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) {
195213 static bool chg_samples [7 ];
196214 static uint8_t chg_index = 0 ;
197215 static struct etimer timer ;
198-
199- // sample at 4Hz
200- etimer_set (& timer , 250 );
216+ static uint32_t charge_count = 0 ;
201217
202218 for (;;) {
219+ etimer_set (& timer , PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME );
203220 PROCESS_WAIT_EVENT_UNTIL (ev == PROCESS_EVENT_TIMER && etimer_expired (& timer ));
204- etimer_restart (& timer );
205221
206222 // Enable charger chip based on USB state. We don't need to disable it
207223 // on charger fault since the chip will automatically disable itself.
@@ -211,6 +227,10 @@ PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) {
211227 chg_samples [chg_index ] = read_chg ();
212228
213229 if (mode_pin_is_low ) {
230+
231+ // Keep track of how long we have been charging.
232+ charge_count ++ ;
233+
214234 // Count number of transitions seen during sampling window.
215235 int transitions = chg_samples [0 ] != chg_samples [PBIO_ARRAY_SIZE (chg_samples ) - 1 ];
216236 for (size_t i = 1 ; i < PBIO_ARRAY_SIZE (chg_samples ); i ++ ) {
@@ -239,12 +259,22 @@ PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) {
239259 // devices) requires a momentary pulse on the /PB pin, which is
240260 // not wired up.
241261 pbdrv_charger_status = PBDRV_CHARGER_STATUS_DISCHARGE ;
262+ charge_count = 0 ;
242263 }
243264
244265 // Increment sampling index with wrap around.
245266 if (++ chg_index >= PBIO_ARRAY_SIZE (chg_samples )) {
246267 chg_index = 0 ;
247268 }
269+
270+ // If we have been charging for a long time, pause charging for a while.
271+ if (charge_count > (PBDRV_CHARGER_MP2639A_CHARGE_TIMEOUT_MS / PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME )) {
272+ pbdrv_charger_status = PBDRV_CHARGER_STATUS_DISCHARGE ;
273+ pbdrv_charger_enable (false, PBDRV_CHARGER_LIMIT_NONE );
274+ etimer_set (& timer , PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS );
275+ PROCESS_WAIT_EVENT_UNTIL (etimer_expired (& timer ));
276+ charge_count = 0 ;
277+ }
248278 }
249279
250280 PROCESS_END ();
0 commit comments