stm32duino / STM32LowPower

Arduino Low Power library for STM32
183 stars 52 forks source link

Memory consumption #90

Closed tshcherban closed 1 year ago

tshcherban commented 1 year ago

Hi I have a project using this library (btw thanks for your recent bugfixes, now sleep modes are acting as needed) with STM32L051C8 and lots of other hardware and logic (LoRa, sensors, communication protocol handling). At some point stuck because i was running out of flash memory. Quite fast I've discovered that the largest consumption is done by two functions: mktime(6572 bytes) and gmtime (9868 bytes), which in turn are used for time calculation from human representation to epoch one and vice-versa. Here are the steps which are executed to set a wakeup alarm:

  1. get rtc actual time (in HH:mm:ss plus subseconds)
  2. convert it with mktime to the epoch representation
  3. add number of milliseconds to that time
  4. set epoch alarm which in turn uses gmtime to convert back to HH:mm:ss

Seems very wasteful to spend ~16kb of flash to just do some time math. Maybe we can add it by hand? i.e.

  if (ms != 0)
  {
      uint16_t subSeconds = ms % 1000;

      ms = ms / 1000;
      uint8_t day = ms / 86400;
      uint8_t hours = (ms - day * 86400) / 3600;
      uint8_t minutes = (ms - day * 86400 - hours * 3600) / 60;
      uint8_t seconds = (ms - day * 86400 - hours * 3600 - minutes * 60);

      rtc.syncDate();
      rtc.syncTime();

      uint32_t ss = rtc.getSubSeconds() + subSeconds;
      if (ss >= 1000)
      {
        ss -= 1000;
        seconds++;
      }

      uint8_t s = rtc.getSeconds() + seconds;
      if (s >= 60)
      {
        s -= 60;
        minutes++;
      }

      uint8_t m = rtc.getMinutes() + minutes;
      if (m >= 60)
      {
        m -= 60;
        hours++;
      }

      uint8_t h = rtc.getHours() + hours;
      if (h >= 24)
      {
        h -= 24;
        day++;
      }

      rtc.setAlarmTime(h, m, s, ss);
      rtc.setAlarmDay(day);
      rtc.enableAlarm(STM32RTC::Alarm_Match::MATCH_DHHMMSS);
  }

That is a code block I have actually used for experiments and seems to be working. It replaces a logic here. Of course its just a draft, need to add AM/PM/24h, month/year handling (if its not ignored by STMs RTC, not sure?). ~Also I would rather add extended RTC library API to set all those parameters at once and avoid explicit or multiple internal calls to RTC_SetXXX and SyncTime/Date~ replace it with a single call to rtc.GetTime(). Of course getEpoch and getY2kEpoch are left intact for those who has plenty of flash and/or need those, but simple sleep/shutdown wouldnt eat too much flash space anymore. P.S. I'm working on a function draft which would do the math and take into account day/month overflow, leap year etc.

fpistm commented 1 year ago

Hi @tshcherban,

yes this is a known topic: https://github.com/stm32duino/STM32LowPower/issues/13 https://github.com/stm32duino/STM32RTC/issues/73

Lowpower uses epoch as it is simple to add offset to the current time. I would suggest to get all values in one time using getTime() to avoid too much calls.

tshcherban commented 1 year ago

I mean maybe we can cut out that 'known topic'? Or you would prefer to stay on the safe side with an epoch approach?

P.S. i saw those tickets above, and of course we can do a manual job here setting time to 00:00:00 and then just alarm time to the needed value. Its obvious if time keeping is not needed, however would be nice to have fully functional library without redundancies out of the box.

P.P.S. does STM32 support waking up by RTC being clocked from external resonator/crystal 32768 Hz? because currently im using default SystemClockConfig (not sure, but seems it uses either LSI or HSI) and time drifts quite a lot, up to 7 seconds for 120s interval in all modes. And also it depends on the temperature, i left my board running on a windowsill, temp was changing over a night from -2°C to +7°C and drift also changed a bit.

fpistm commented 1 year ago

Main concern is about performance, I think using date and time would take more time to execute but is it really an issue? 😉 Do not hesitate to provide a PR then we will review and test.

Setting RTC time to 00:00:00 is correct only if it is not already initialized. Yes the wake up from RTC clocked by external oscillator is supported, some mode even required specifically to use LSE: https://github.com/stm32duino/STM32LowPower/blob/ef798e3200a8d136f0062eaeeeac3076433ac0e3/src/STM32LowPower.cpp#L187-L207

tshcherban commented 1 year ago

Ok, I'll create a PR and also measure a performance. BTW mktime and gmtime are taking ~124 and ~60 microseconds respectively (measured using a micros() call, Nucleo64-L053R8), so in total its like ~180 us. Will compare it to a custom approach.

tshcherban commented 1 year ago

Preliminary results are: 173 us for epoch approach vs 64 us for custom calculation approach (measured the part with epoch/custom calculation, custom calculation includes getTime/getDate calls). Also reduced by ~15k of flash and 350 bytes of RAM. All above valid for STM32L051C8/STM32L053C8, optimize -Os, Newlib Nano.