Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu/esp8266: add RTT implementation #13640

Merged
merged 1 commit into from
Mar 19, 2020

Conversation

gschorcht
Copy link
Contributor

@gschorcht gschorcht commented Mar 16, 2020

Contribution description

This PR adds an RTT implementation for the ESP8266.

The ESP8266 RTC module integrates a RTT. Unfortunately it is completely undocumented. It is not known what interrupts it has, nor how they are configured, nor how they are used by the binary SDK libraries. The only available information about this RTT is that it is driven by a 160 kHz RC oscillator and how the counter value can be read.

Therefore, this RTT implementation uses a combination of the hardware RTT and a CPU counter. While the CPU counter is used to count and handle interrupts when the CPU is active, the hardware RTT is used to maintain the counter during reboot or deep sleep. For this purpose, the values of both the CPU counter and the RTT counter are stored in RTC RAM when the CPU enters the reboot or deep sleep. After the restart, the CPU counter is reinitialized with the values of the RTC counter updated by RTT counter progress.

The CPU counter is clocked by a PLL-driven 80 MHz clock divided by 256. This results into a RTT frequency of 312 kHz. That is, the maximum time that can be used with this RTT are about 3 hours, 49 mins and 3 seconds.

Testing procedure

Use tests/periph_rtt to test the counter

make BOARD=esp8266-esp-12x -C tests/periph_rtt flash test

To test the preservation of the counter during the restart (requires PR #13638):

FEATURES_REQUIRED=periph_rtc make BOARD=esp8266-esp-12x -C tests/shell flash term

> rtt get
2296562
> reboot
reboot
> rtt get
4704894

Issues/PRs references

RTT based RTC implementation in PR #13519 can then be used to realize a RTC.

cpu/esp8266/startup.c Outdated Show resolved Hide resolved
{
/* save counters before going to sleep or reboot */
_rtt_counter_saved = frc2.count;
_rtc_counter_saved = RTC.COUNTER;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does RTC.COUNTER come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In contrast to all other peripherals for which the SDK defines the registers as a struct, the registers of the RTC module aren't defined by the SDK at all, see rtc_register.h.

Therefore, another piece of vendor code is used which was initially extracted from the esp-open-rtos. It tries to define the RTC registers as a struct. BTW, this struct is defined exactly in the way you mentioned with volatile members and without linker stuff.

ESP8266 is a terrible platform. It has the worst documentation I have ever seen and there is no consistent development environment.

cpu/esp8266/periph/rtt.c Outdated Show resolved Hide resolved

void rtt_set_alarm(uint32_t alarm, rtt_cb_t cb, void *arg)
{
rtt_config.alarm = alarm;
Copy link
Contributor

@benpicco benpicco Mar 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alarm can happen after the wrap around, so the overflow callback should be triggered before.

e.g. frc2.count = 1000, rtt_set_alarm(50, …) is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. It requires also to change the interrupt handling in _rtt_cb.

BTW, configured alarms will not survive deep sleep and reboots. Otherwise the whole rtt_config_t structure would have to be placed in RTC RAM which has only 256 bytes. But since the system is restarted after a reboot or deep sleep, preserving configured interrupts makes no sense.

Copy link
Contributor Author

@gschorcht gschorcht Mar 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, seems to work now:

> rtt setalarm 1000
> rtt set 4294967290
The alarm rang

> rtt setoverflow
> rtt setalarm 1000
> rtt set 4294967290
RTT overflow
The alarm rang

> rtt setalarm 4294967295
> rtt set 4294967290
The alarm rang
RTT overflow

> rtt set 4294967290
RTT overflow

> rtt setalarm 2000000000
> rtt set 4294967290
RTT overflow

> rtt set 1999999990
The alarm rang

Because of the interrupt latency, the only case that cannot be handled with this emulated overflow interrupt is when the alarm is set to 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the interrupt latency, the only case that cannot be handled with this emulated overflow interrupt is when the alarm is set to 0.

But if you can set an overflow 'alarm' you can also set an alarm at 0, they should happen at the same time.

Copy link
Contributor Author

@gschorcht gschorcht Mar 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benpicco Thanks for your suggestions. I had simpler solution already

> rtt setoverflow
> rtt setalarm 0
> rtt set 4294967290
The alarm rang
RTT overflow

> rtt set 4294967290
RTT overflow

> rtt setalarm 1
> rtt set 4294967290
RTT overflow
The alarm rang

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My problem was that I tried to test the current counter value, which of course can be larger than the configured alarm value due to the interrupt latency.

So the solution was not to test the actual counter value but the configured alarm value. Since _rtt_cb is always called for the actually set alarm value, the value in the frc2.alarm register is always exactly the relevant alarm.


void rtt_set_alarm(uint32_t alarm, rtt_cb_t cb, void *arg)
{
rtt_config.alarm = alarm;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the interrupt latency, the only case that cannot be handled with this emulated overflow interrupt is when the alarm is set to 0.

But if you can set an overflow 'alarm' you can also set an alarm at 0, they should happen at the same time.

cpu/esp8266/periph/rtt.c Outdated Show resolved Hide resolved
cpu/esp8266/periph/rtt.c Show resolved Hide resolved
cpu/esp8266/periph/rtt.c Outdated Show resolved Hide resolved
cpu/esp8266/periph/rtt.c Outdated Show resolved Hide resolved
cpu/esp8266/periph/rtt.c Outdated Show resolved Hide resolved
Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good and works like a charm!
Please squash.

@benpicco benpicco added Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT labels Mar 18, 2020
@benpicco
Copy link
Contributor

Murdock isn't happy yet.

@gschorcht
Copy link
Contributor Author

Murdock isn't happy yet.

@benpicco I pushed a fixup and added already the BACKUP_RAM attribute so that the RTT based RTC in PR #13519 will work out of the box.

@benpicco
Copy link
Contributor

This should make Murdock happy, please squash directly.

@gschorcht gschorcht added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Mar 19, 2020
@gschorcht
Copy link
Contributor Author

gschorcht commented Mar 19, 2020

I had to remove and set CI ready to build because the error in Murdock was not related to this PR.

@benpicco benpicco merged commit 829671d into RIOT-OS:master Mar 19, 2020
@gschorcht
Copy link
Contributor Author

@benpicco Thanks for reviewing, testing and merging.

@gschorcht gschorcht deleted the cpu/esp8266/periph_rtt branch March 19, 2020 17:03
@gschorcht
Copy link
Contributor Author

gschorcht commented Mar 25, 2020

@benpicco I need some advice regarding this RTT implementation. You have a lot of experience with RTT implementations.

The background:

Finally I was able to figure out the reason for the instability of the upgrade for ESP8266 RTOS SDK version 3.3. This version of the SDK now uses the FRC2 as WiFi timer for WiFi power management time and other PHY management functions. This is new with this version of the SDK: concerned: This means that the RTT implementation, which was also based on this FRC2 in active CPU mode, will no longer work.

Simply using the existing RTC counter is still not possible because it is still not clear how to enable the alarm interrupt. It is completely undocumented. Furthermore, it seems that is also used by the SoC phy SDK library, at least during startup.

A possible solution would be to use the WiFi timer, which provides an accurate millisecond timer API, to emulate the RTT in active CPU mode, and use the RTC counter in sleep mode as before.

The question

Which RTT_FREQUENCY makes sense in this case? We have 3 options:

  1. We use the RTC counter frequency. The RTC counter frequency is about 160 kHz and drifting a lot because its clock is an RC oscillator. It might become quite difficult to place WiFi timer alarms that are synchronous with the RTC counter value.
  2. We use a common frequency, lets say 1000 Hz. The RTT implementation would hold its own counter that would have to be updated every millisecond which in turn would require an WiFi timer interrupt with this frequency.
  3. We use 1 Hz as frequency. Even though the RTT implementation would also hold its own counter, it would have to be updated only once a second.

Option 3 would be the easiest one. For RTT based RTC implementation that would be fine. But is that still a counter as usually required. Usual RTTs are driven by 32.768 kHz crystals. But, I have seen a number of boards and CPUs that also use a 1 Hz counter. But having a 1 Hz RTT and an RTC based on this RTT is better than nothing.

What do you think?

@leandrolanzieri leandrolanzieri added this to the Release 2020.04 milestone Mar 26, 2020
@benpicco
Copy link
Contributor

benpicco commented Mar 26, 2020

Hm but if you can't use the alarm interrupt of the RTC counter, how does it help you in sleep mode?
The RTT/RTC are expected to keep running during sleep and are usually the only remaining wake-up sources in deep sleep besides GPIOs.

@gschorcht
Copy link
Contributor Author

Hm but if you can't use the alarm interrupt of the RTC counter, how does it help you in sleep mode?

It is of course possible to activate an interrupt for this. But it is not known how. The application simply calls either esp_deep_sleep(microseconds) or esp_light_sleep_start after an RTC alarm is set. Everything else in the hell of undocumented ESP8266 RTC is done by the SDK binaries.

I spent several days disassembling all the code and figuring out how to activate and use the interrupt without any progress. Also in other projects several cracks have tried to solve the puzzle of the RCT in ESP8266. The resutl is something like that:

struct RTC_REGS {
    uint32_t volatile CTRL0;        // 0x00
    uint32_t volatile COUNTER_ALARM;    // 0x04
    uint32_t volatile RESET_REASON0;    // 0x08       //FIXME: need better name
    uint32_t volatile _unknownc;        // 0x0c
    uint32_t volatile _unknown10;       // 0x10
    uint32_t volatile RESET_REASON1;    // 0x14       //FIXME: need better name
    uint32_t volatile RESET_REASON2;    // 0x18       //FIXME: need better name
    uint32_t volatile COUNTER;          // 0x1c
    uint32_t volatile INT_SET;          // 0x20
    uint32_t volatile INT_CLEAR;        // 0x24
    uint32_t volatile INT_ENABLE;       // 0x28
    uint32_t volatile _unknown2c;       // 0x2c
    uint32_t volatile SCRATCH[4];       // 0x30 - 3c
    uint32_t volatile _unknown40;       // 0x40
    uint32_t volatile _unknown44;       // 0x44
    uint32_t volatile _unknown48;       // 0x48
    uint32_t volatile _unknown4c[7];    // 0x4c - 0x64
    uint32_t volatile GPIO_OUT;         // 0x68
    uint32_t volatile _unknown6c[2];    // 0x6c - 0x70
    uint32_t volatile GPIO_ENABLE;      // 0x74
    uint32_t volatile _unknown80[5];    // 0x78 - 0x88
    uint32_t volatile GPIO_IN;          // 0x8c
    uint32_t volatile GPIO_CONF;        // 0x90
    uint32_t volatile GPIO_CFG[6];      // 0x94 - 0xa8
};

The RTT/RTC are expected to keep running during sleep and are usually the only remaining wake-up sources in deep sleep besides GPIOs.

It is working in that way as long as we rely on SDK functions. The problem that we can't control the hardware directly because we have no information how 😟

@benpicco
Copy link
Contributor

Hm that's gnarly.
Typically you'd want to use the RTT if you want to sleep, otherwise you might just use any hardware timer.

So I would say use the frequency of the 160 kHz RTC counter, so you can align sleep times with that.
Then use another timer to emulate a 160 kHz timer to run in active mode.
They timers don't have to be in sync, you'd only sync them before going to sleep (active timer -> RTC) and after wake-up (RTC -> active timer).

At least that's how I understood it, not sure if this is possible with the hardware / available documentation 😕

@gschorcht
Copy link
Contributor Author

So I would say use the frequency of the 160 kHz RTC counter, so you can align sleep times with that.
Then use another timer to emulate a 160 kHz timer to run in active mode.
They timers don't have to be in sync, you'd only sync them before going to sleep (active timer -> RTC) and after wake-up (RTC -> active timer).

This is exactly how it is implemented in the ESP32 RTC if it hasn't connected an external 32.768kHz crystal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants