adafruit / Adafruit_nRF52_Arduino

Adafruit code for the Nordic nRF52 BLE SoC on Arduino
Other
615 stars 496 forks source link

micros() function based RTOS tick of 1024 herts #451

Closed groupblabs closed 4 years ago

groupblabs commented 4 years ago

Describe the bug The micros() function is based on an RTOS tick of 1024hz; cannot count at increments lower than ~1ms.

Hardware and Environment Information: Hardware model: Model: Adafruit Feather nRF52840 Express Hardware revision: nRF52840-Feather-revD Bootloader version: UF2 Bootloader 0.3.0 lib/nrfx (v2.0.0) lib/tinyusb (legacy-1500-g23df777b) s140 6.1.1 Arduino IDE version 1.8.9 Adafruit nRF52 Arduino Board Package version 0.14.6 Product P/N: 15025 Product URL: https://www.adafruit.com/product/4062

Sample Code to Repro Issue

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  Serial.println(micros());
}

To Reproduce Steps to reproduce the behavior:

  1. Setup Arduino IDE for Adafruit Feather nRF52840 Express
  2. Copy Sample Code into IDE (see above)
  3. Upload Sample Code to Adafruit Feather nRF52840 Express
  4. Open Serial Monitor and observe micros() function output

Expected behavior micros() function output increments at a resolution comparable to Arduino specifications (i.e. 4us for a 16MHz Arduino boards). [https://www.arduino.cc/en/pmwiki.php%3Fn%3DReference/Micros](Arduino micros() function reference)

Screenshots Arduino_Uno_Micros_Output

Teensy_Micros_Output

Adaruit_Feather_nRF52840_Micros_Output

Additional context Adafruit forum thread on this issue: [https://forums.adafruit.com/viewtopic.php?f=53&t=162235&p=802573](Adafruit Feather nRF52840 Express - micros() counter function resolution very low)

tannewt commented 4 years ago

Why do you need the added resolution? Context helps prioritization.

groupblabs commented 4 years ago

Hi Scott,

Thanks for reaching out. I am working on a project that uses a high-speed ultrasonic distance sensor. This sensor uses ultrasonic acoustic echo detection that can be used to calculate distance. Being a sort-range sensor, the time delta between pulse emission and echo detection is generally between 25 and 500 microseconds in a typical use case, though this range can be lower and/or higher depending on the use-case and application.

The way it works is like this: the sensor triggers and interrupt as soon as it fires an ultrasonic acoustic pulse and triggers a second interrupt as soon as it detects an echo from that pulse. The code running behind the sensor, meanwhile, employs interrupt handlers for each of the 2 interrupts triggered by the sensor. The first interrupt handler is triggered as the sensor emits a pulse while the second interrupt handler is triggered as soon as the sensor detects an echo from the emitted pulse. Both of these interrupt handlers simply capture a timestamp in microseconds by calling the micros() function and writing the returned microsecond value to an unsigned long. The software calculates the pulse-echo time delta by subtracting the timestamp of the echo interrupt from the timestamp of pulse interrupt (i.e. typically between 25us and 500us). Given this time delta in microseconds, we can the formulaically calculate distance using an equation.

We purchased a few of the Adafruit nRF52840 Feather Express boards along with feather accessories, whose wireless capabilities, onboard features, and add-on options we hoped to leverage on this and future projects. Unfortunately, given the counter resolution of the Adafruit nRF52840 Feather Express, I am unable to use it on this or similar projects requiring precision timing less than 1ms.

I hope this explanation helps give you context into our use-case and problem. Please let me know if you have any further questions. Thanks!

Kind regards, Illi

On Mon, Mar 9, 2020 at 4:31 PM Scott Shawcroft notifications@github.com wrote:

Why do you need the added resolution? Context helps prioritization.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/451?email_source=notifications&email_token=AN6IJ4DIBOPCOIYSC5YJLV3RGWC65A5CNFSM4LEP65IKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOJN6GA#issuecomment-596827928, or unsubscribe https://github.com/notifications/unsubscribe-auth/AN6IJ4GFBOV7Z2K6ZGDZN6DRGWC65ANCNFSM4LEP65IA .

jpconstantineau commented 4 years ago

Putting a comment on this...

The minimum resolution the RTC is 30.517 μs. See page 334 of the specs: https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.0.pdf

I assume FreeRTOS uses one of the RTC for keeping "millis".

Would simply reading the RTC counter and making the difference work? There are 3 counters, each with start/stop events. That might be handy and be more efficient in your timing routines. Not sure if any are available considering the softdevice and FreeRTOS. If one is available, I would suggest going directly to the counters and prototype it.

On Mon, 16 Mar 2020 at 12:11, groupblabs notifications@github.com wrote:

Hi Scott,

Thanks for reaching out. I am working on a project that uses a high-speed ultrasonic distance sensor. This sensor uses ultrasonic acoustic echo detection that can be used to calculate distance. Being a sort-range sensor, the time delta between pulse emission and echo detection is generally between 25 and 500 microseconds in a typical use case, though this range can be lower and/or higher depending on the use-case and application.

The way it works is like this: the sensor triggers and interrupt as soon as it fires an ultrasonic acoustic pulse and triggers a second interrupt as soon as it detects an echo from that pulse. The code running behind the sensor, meanwhile, employs interrupt handlers for each of the 2 interrupts triggered by the sensor. The first interrupt handler is triggered as the sensor emits a pulse while the second interrupt handler is triggered as soon as the sensor detects an echo from the emitted pulse. Both of these interrupt handlers simply capture a timestamp in microseconds by calling the micros() function and writing the returned microsecond value to an unsigned long. The software calculates the pulse-echo time delta by subtracting the timestamp of the echo interrupt from the timestamp of pulse interrupt (i.e. typically between 25us and 500us). Given this time delta in microseconds, we can the formulaically calculate distance using an equation.

We purchased a few of the Adafruit nRF52840 Feather Express boards along with feather accessories, whose wireless capabilities, onboard features, and add-on options we hoped to leverage on this and future projects. Unfortunately, given the counter resolution of the Adafruit nRF52840 Feather Express, I am unable to use it on this or similar projects requiring precision timing less than 1ms.

I hope this explanation helps give you context into our use-case and problem. Please let me know if you have any further questions. Thanks!

Kind regards, Illi

On Mon, Mar 9, 2020 at 4:31 PM Scott Shawcroft notifications@github.com wrote:

Why do you need the added resolution? Context helps prioritization.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub < https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/451?email_source=notifications&email_token=AN6IJ4DIBOPCOIYSC5YJLV3RGWC65A5CNFSM4LEP65IKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOJN6GA#issuecomment-596827928 , or unsubscribe < https://github.com/notifications/unsubscribe-auth/AN6IJ4GFBOV7Z2K6ZGDZN6DRGWC65ANCNFSM4LEP65IA

.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/451#issuecomment-599686786, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAIYW6ETO6VPBREJXHBZLSDRHZTWJANCNFSM4LEP65IA .

-- Pierre

tannewt commented 4 years ago

That is a great explanation. Thanks!

I don't work on Arduino myself but other folks can chime in here.

In the meantime you could look into running your own timer for the timestamp. I believe that is possible with Arduino but don't know the details.

henrygab commented 4 years ago
Don't enable bluetooth (SoftDevice)

NOTE: if you enable the SoftDevice, you will **_NOT_** be able to guarantee timings, because BLE interrupts will interfere sporadically. I know of no workaround at this time with this BSP. In addition, if you enable the softdevice, then various peripherals are restricted. From nordic S140_SDS v2.1:

Definition of access types

Access type | Definition --- | --- Restricted | The hardware peripheral is used by the SoftDevice and is outside the application sandbox. When the SoftDevice is enabled, it shall only be accessed through the SoftDevice API. Through this API, the application has limited access. Blocked | The hardware peripheral is used by the SoftDevice and is outside the application sandbox. The application has no access. Interrupts from blocked peripherals are forwarded to the SoftDevice by the MBR and are not available to the application, even inside a Radio Timeslot API timeslot. Open | The hardware peripheral is not used by the SoftDevice. The application has full access.

Peripheral Summary

Instance | Access SoftDevice enabled | Access SoftDevice disabled --- | --- | --- CLOCK | Restricted | Open POWER | Restricted | Open RADIO | Blocked(3) | Open TIMER0 | Blocked(3) | Open RTC0 | Blocked | Open TEMP | Restricted | Open RNG | Restricted | Open ECB | Restricted | Open CCM | Blocked4 | Open AAR | Blocked4 | Open EGU1/SWI1/Radio Notification | Restricted(5) | Open EGU5/SWI5 | Blocked | Open ACL | Restricted | Open NVMC | Restricted | Open MWU | Restricted6 | Open FICR | Blocked | Blocked UICR | Restricted | Open NVIC | Restricted(7) | Open

Based on the above table, each of RTC0, TIMER0 are blocked and inaccessible once the SoftDevice is enabled.

The nRF52840 implements DWT, including a cycle count register.

Here's some links to getting cycle counts using the DWT peripheral.

henrygab commented 4 years ago

Hmmm.... actually, the cycle count may not be reliable for timing; Depends on whether the system enters low-power (tickless) mode or not, and whether the CPU is stopped at that point.

fgaetani commented 4 years ago

Hi, I have the same problem! There is a solution? Thanks

pyro9 commented 4 years ago

@henrygab Just for grins, I copy pasted the code from the good information you suggested and ran it on a nrf52832 I had at hand and it seems to work as-is.

@groupblabs +/- interrupt latency and low-power mode, it looks like that can work.

#include <bluefruit.h>

/* DWT (Data Watchpoint and Trace) registers, only exists on ARM Cortex with a DWT unit */
#define KIN1_DWT_CONTROL             (*((volatile uint32_t*)0xE0001000))
/*!< DWT Control register */

#define KIN1_DWT_CYCCNTENA_BIT       (1UL<<0)
/*!< CYCCNTENA bit in DWT_CONTROL register */

#define KIN1_DWT_CYCCNT              (*((volatile uint32_t*)0xE0001004))
/*!< DWT Cycle Counter register */

#define KIN1_DEMCR                   (*((volatile uint32_t*)0xE000EDFC))
/*!< DEMCR: Debug Exception and Monitor Control Register */

#define KIN1_TRCENA_BIT              (1UL<<24)
/*!< Trace enable bit in DEMCR register */

#define KIN1_InitCycleCounter() \
  KIN1_DEMCR |= KIN1_TRCENA_BIT
  /*!< TRCENA: Enable trace and debug block DEMCR (Debug Exception and Monitor Control Register */

#define KIN1_ResetCycleCounter() \
  KIN1_DWT_CYCCNT = 0
  /*!< Reset cycle counter */

#define KIN1_EnableCycleCounter() \
  KIN1_DWT_CONTROL |= KIN1_DWT_CYCCNTENA_BIT
  /*!< Enable cycle counter */

#define KIN1_DisableCycleCounter() \
  KIN1_DWT_CONTROL &= ~KIN1_DWT_CYCCNTENA_BIT
  /*!< Disable cycle counter */

#define KIN1_GetCycleCounter() \
  KIN1_DWT_CYCCNT
  /*!< Read cycle counter register */

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(LED_RED, OUTPUT);
  digitalWrite(LED_RED, 1);
  Bluefruit.begin(0, 1);
  while(!Serial)
    yield;
}

void loop() {
  static int i=0;
  static uint32_t cycles=0; /* number of cycles */

  KIN1_InitCycleCounter(); /* enable DWT hardware */
  KIN1_ResetCycleCounter(); /* reset cycle counter */
  KIN1_EnableCycleCounter(); /* start counting */

  // put your main code here, to run repeatedly:
  if (Serial)
      Serial.printf("%u\n",cycles);

  digitalWrite(LED_RED, ((millis()/500)&1));
  cycles = KIN1_GetCycleCounter(); /* get cycle counter */
  KIN1_DisableCycleCounter(); /* disable counting if not used any more */
}
hathach commented 4 years ago

hi hi, It has been quite a bit of delay from my side (doing multiple things at once). But I have go through the nrf52 datasheet, there is no direct cpu cycle register. RTC1 is configured as close to 1ms (1024) tick for freeRTOS. There isn't any way to read the internal <<presc>> counter as well within the RTC1.

image

Anyway, the only way to have a sub freeRTOS tick resolution is enabling extra timer. DWT is a great choice, since it count cpu cycle, and is within the MCU Core which consumes much less power than "external" timer peripheral. However doing so by default still increases power consumption unnecessarily for most project. Therefore here is my thought:

As @henrygab has pointed out, when in low-power (sleep) the CPU is suspended and so does the DWT cyclecount. Though I think we can try to "sync" DWT cycle with the freeRTOS ticks (RTC tick is still updated waking from sleep mode). I will try to put up something for you to test.

hathach commented 4 years ago

@groupblabs would you mind trying out the pr #551 to see if that fixed your issue. You will need to manual enable dwt in setup function for power consumption reason mentioned above. micro() is adjusted after waking from sleep as well.

void setup()
{
  dwt_enable();
  Serial.begin(115200);
}

void loop()
{
  Serial.println(micros());
}