【问题标题】:Arduino's micros() method is taking over 500 cycles to execute on Nano 33 BLE SenseArduino micros() 方法在 Nano 33 BLE Sense 上执行超过 500 个周期
【发布时间】:2021-11-11 06:55:14
【问题描述】:

我首先在 Arduino 论坛上发布了这个问题,但我的问题最近没有受到太多关注,所以这里......

我非常努力地制作了一个简单的草图,以证明 micros() 方法在 Nano 33 BLE Sense 上的速度有多慢:

#include "mbed.h"
#include "nrf_delay.h"
#include "nrf_gpio.h"

#define OUTPUT_PIN NRF_GPIO_PIN_MAP(1,11)

unsigned long startClock;
unsigned long stopClock;
unsigned long arithmeticMinuend = 4294967295;
unsigned long arithmeticSubtrahend = 2147483648;
unsigned long arithmeticDifference = 0;
unsigned long timeDifference1 = 0;
unsigned long timeDifference2 = 0;

//Assume 1 to 4 clock cycles (15.625 to 62.5 ns) is required for every...
//* call of micros();
//* subtraction of two unsigned longs.
//Therefore we should not be able to measure the time to execute either task by using the micros() - startClock technique, as...
//* micros() - startClock = 0 in either case.

void setup() {
  Serial.begin(115200);
  while (!Serial);

  nrf_gpio_cfg_output(OUTPUT_PIN);

  startClock = micros();
  stopClock = micros();
}

void loop() {
  timeDifference1 = microsecondsToSubtract2LongsOnce();
  timeDifference2 = microsecondsToSubtract2LongsTwice();
  Serial.print("timeDifference1 = ");
  Serial.print(timeDifference1);
  Serial.print(" us.\ntimeDifference2 = ");
  Serial.print(timeDifference2);
  Serial.println(" us.");
  
  microsecondsToSubtract2LongsOnceMeasuredWithScope();
  delay(100);
  microsecondsToSubtract2LongsTwiceMeasuredWithScope();
  delay(100);
  microsecondsToToggleOutputPinMeasuredWithScope();
  
  while(1) {
    
  }
}

//Prints 8 us:
unsigned long microsecondsToSubtract2LongsOnce() {
  startClock = micros();
  stopClock = micros();
  return stopClock - startClock;
}

//Prints 9 us:
unsigned long microsecondsToSubtract2LongsTwice() {
  startClock = micros();
  arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
  stopClock = micros();
  return stopClock - startClock;
}

//Measured 18 us on scope:
void microsecondsToSubtract2LongsOnceMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  startClock = micros();
  stopClock = micros();
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

//Measured 18 us on scope:
void microsecondsToSubtract2LongsTwiceMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  startClock = micros();
  arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
  stopClock = micros();
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

//Measured 500 ns on scope:
void microsecondsToToggleOutputPinMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

我将使用示波器进行的时间测量与使用 micros() 进行的时间测量进行比较。通过数学计算,我们得到一个 micros() 调用所需的时钟周期数 (CC) = (0.5*(18000 - 500) ns)/15.625 ns = 560 CC!!!

有人可以举个例子来测量这个板上不需要超过 10 CC 的时间吗???

根据我的阅读,我认为更快的 (theoretically 1 CC) 策略将涉及使用方法 nrf_drv_timer 或更新的 nrfx_timer(两种策略都需要设置计时器以在计数器模式下运行),但我不能找不到在我的 Arduino 的 NRF52840 上使用的具体示例。


编辑:

我也尝试使用 mbed 的 us_ticker 来缩短时间,但计时结果完全相同。这是我用于该测试的代码:

#include "mbed.h"
#include "nrf_delay.h"
#include "nrf_gpio.h"

#define OUTPUT_PIN NRF_GPIO_PIN_MAP(1,11)

mbed::Timer timer;

uint32_t startClock;
uint32_t stopClock;
uint32_t arithmeticMinuend = 4294967295;
uint32_t arithmeticSubtrahend = 2147483648;
uint32_t arithmeticDifference = 0;
uint32_t timeDifference1 = 0;
uint32_t timeDifference2 = 0;

//Assume 1 to 4 clock cycles (15.625 to 62.5 ns) is required for every...
//* call of micros();
//* subtraction of two unsigned longs.
//Therefore we should not be able to measure the time to execute either task by using the micros() - startClock technique, as...
//* micros() - startClock = 0 in either case.

void setup() {
  Serial.begin(115200);
  while (!Serial);

  nrf_gpio_cfg_output(OUTPUT_PIN);
  timer.start();
  startClock = timer.read_us();
  stopClock = timer.read_us();
}

void loop() {
  timeDifference1 = microsecondsToSubtract2LongsOnce();
  timeDifference2 = microsecondsToSubtract2LongsTwice();
  Serial.print("timeDifference1 = ");
  Serial.print(timeDifference1);
  Serial.print(" us.\ntimeDifference2 = ");
  Serial.print(timeDifference2);
  Serial.println(" us.");
  
  microsecondsToSubtract2LongsOnceMeasuredWithScope();
  timer.stop();
  delay(100);
  timer.start();
  microsecondsToSubtract2LongsTwiceMeasuredWithScope();
  timer.stop();
  delay(100);
  timer.start();
  microsecondsToToggleOutputPinMeasuredWithScope();
  timer.stop();
  
  while(1) {
    
  }
}

//Prints 8 us:
unsigned long microsecondsToSubtract2LongsOnce() {
  startClock = timer.read_us();
  stopClock = timer.read_us();
  return stopClock - startClock;
}

//Prints 9 us:
unsigned long microsecondsToSubtract2LongsTwice() {
  startClock = timer.read_us();
  arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
  stopClock = timer.read_us();
  return stopClock - startClock;
}

//Measured 18 us on scope:
void microsecondsToSubtract2LongsOnceMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  startClock = timer.read_us();
  stopClock = timer.read_us();
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

//Measured 18 us on scope:
void microsecondsToSubtract2LongsTwiceMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  startClock = timer.read_us();
  arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
  stopClock = timer.read_us();
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

//Measured 500 ns on scope:
void microsecondsToToggleOutputPinMeasuredWithScope() {
  nrf_gpio_pin_toggle(OUTPUT_PIN);
  nrf_gpio_pin_toggle(OUTPUT_PIN);
}

【问题讨论】:

  • micros() 使用 mbed 的计时器实现。参见 core 中的 Wiring.cpp
  • 您使用示波器真是太好了。放弃图书馆会让你更接近。不要相信任何图书馆调用,除非你能确认它做了它应该做的事情。

标签: timer arduino timing nrf52 arduino-nano


【解决方案1】:

我的“提示”,供参考:

对于短时间间隔,您应该能够从 ARM Core SysTick Timer 或 ARM DWT Cycle Counter 读取循环计数(您可能必须先启用 DWT。)

    startTime = SysTick->VAL; // 24bit counter, probably resets at ~1ms.  Counts Down!
// or
    start = DWT->CYCCNT;  //CYCCNT wraps at 32bits; never resets, counts up.
// With:
  // Somewhere in system init
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

我不知道这些将如何受到(或影响)低功耗模式等的影响。 (特别是DWT可能会用电,不知道在掉电模式下是否会关机。)

【讨论】:

    【解决方案2】:

    这是使用 mBed 的 us_ticker_read 的策略,它比 Arduino 的 micros 或 mBed 的 read_us 快一个数量级以上,即 650 ns42 CC

    #include "mbed.h"
    #include "nrf_delay.h"
    #include "nrf_gpio.h"
    
    #define OUTPUT_PIN NRF_GPIO_PIN_MAP(1,11)
    
    uint32_t startClock;
    uint32_t stopClock;
    uint32_t arithmeticMinuend = 4294967295;
    uint32_t arithmeticSubtrahend = 2147483648;
    uint32_t arithmeticDifference = 0;
    uint32_t timeDifference1 = 0;
    uint32_t timeDifference2 = 0;
    
    //Assume 1 to 4 clock cycles (15.625 to 62.5 ns) is required for every...
    //* call of micros();
    //* subtraction of two unsigned longs.
    //Therefore we should not be able to measure the time to execute either task by using the micros() - startClock technique, as...
    //* micros() - startClock = 0 in either case.
    
    void setup() {
      Serial.begin(115200);
      while (!Serial);
    
      nrf_gpio_cfg_output(OUTPUT_PIN);
      us_ticker_init();
      startClock = us_ticker_read();
      stopClock = us_ticker_read();
    }
    
    void loop() {
      Serial.println("Starting program:");
      timeDifference1 = microsecondsToSubtract2LongsOnce();
      timeDifference2 = microsecondsToSubtract2LongsTwice();
      us_ticker_free();
      Serial.print("timeDifference1 = ");
      Serial.print(timeDifference1);
      Serial.print(" us.\ntimeDifference2 = ");
      Serial.print(timeDifference2);
      Serial.println(" us.");
    
      us_ticker_init();
      microsecondsToSubtract2LongsOnceMeasuredWithScope();
      us_ticker_free();
      delay(10);
      us_ticker_init();
      microsecondsToSubtract2LongsTwiceMeasuredWithScope();
      us_ticker_free();
      delay(10);
      us_ticker_init();
      microsecondsToToggleOutputPinMeasuredWithScope();
      us_ticker_free();
      
      while(1) {
        
      }
    }
    
    //Prints 1 us:
    uint32_t microsecondsToSubtract2LongsOnce() {
      startClock = us_ticker_read();
      stopClock = us_ticker_read();
      return stopClock - startClock;
    }
    
    //Prints 1 us:
    uint32_t microsecondsToSubtract2LongsTwice() {
      startClock = us_ticker_read();
      arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
      stopClock = us_ticker_read();
      return stopClock - startClock;
    }
    
    //Measured 1.8 us on scope:
    void microsecondsToSubtract2LongsOnceMeasuredWithScope() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      startClock = us_ticker_read();
      stopClock = us_ticker_read();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    
    //Measured 2 us on scope:
    void microsecondsToSubtract2LongsTwiceMeasuredWithScope() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      startClock = us_ticker_read();
      arithmeticDifference = arithmeticMinuend - arithmeticSubtrahend;
      stopClock = us_ticker_read();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    
    //Measured 500 ns on scope:
    void microsecondsToToggleOutputPinMeasuredWithScope() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    

    如果有人可以在接下来的几天内发布一个更快的示例,我会接受他们的回答! ?


    编辑(2021 年 11 月 12 日):

    我应该感谢 Arduino 论坛上的 westfw 引导我朝着正确的方向前进。他建议的策略非常准确,只需 1 或 2 个 CC 即可执行:

    #include "mbed.h"
    #include "nrf.h"
    #include "nrf_delay.h"
    #include "nrf_gpio.h"
    
    #define OUTPUT_PIN NRF_GPIO_PIN_MAP(1,11)
    
    const float MICROS_PER_CYCLE = 0.015625; //Nano 33 BLE Sense operates at 64 MHz -> 15.625 ns/cc.
    const byte BYTE_TO_SEND = 170; //b'10101010'.
    const int BYTES_PER_TRANSFER = 54;
    
    byte buffer[BYTES_PER_TRANSFER];
    
    volatile uint32_t stopWatchTime1;
    volatile uint32_t stopWatchTime2;
    volatile uint32_t stopWatchTime3;
    
    void setup(){
      Serial.begin(1000000); //Does nothing on the Nano 33 BLE Sense.
      while (!Serial); //Wait for serial port to connect. Needed for native USB CDC on Nano 33 BLE Sense.
    
      stopWatchInit();
      
      nrf_gpio_cfg_output(OUTPUT_PIN); //Configure pin as digital output for measuring time of events on scope.
      
      memset(buffer, BYTE_TO_SEND, sizeof(buffer));
    
      //Measured 300 ns on scope and printed 190 ns.
      togglePinTwice();
      delay(10);
    
      //Measured 840 ns on scope and printed 730 ns:
      togglePinTwiceAndCallMicrosTwice();
      delay(10);
    
      //Measured ~68 us on scope and printed 67.69 us:
      togglePinTwiceCallMicrosTwiceAndWriteBytes(); //This result makes sense bc 88 - 9.2 = 78.8 us (which is basically 78 us).
      delay(10);
    
      //Measured ~68 us on scope:
      togglePinTwiceAndWriteBytes();
    
      Serial.println("");
      Serial.print(MICROS_PER_CYCLE*((float)stopWatchTime1));
      Serial.println(" us");
      Serial.println("");
      Serial.print(MICROS_PER_CYCLE*((float)stopWatchTime2));
      Serial.println(" us");
      Serial.println("");
      Serial.print(MICROS_PER_CYCLE*((float)stopWatchTime3));
      Serial.println(" us");
    }
      
    void loop(){}
    
    void togglePinTwice(){
      stopWatchStart();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      stopWatchTime1 = stopWatchGet();
    }
    
    void togglePinTwiceAndCallMicrosTwice() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      stopWatchStart();
      stopWatchTime2 = stopWatchGet();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    
    void togglePinTwiceCallMicrosTwiceAndWriteBytes() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      stopWatchStart();
      Serial.write(buffer, sizeof(buffer));
      Serial.flush(); //Patch current Serial.h to use or it will block forever.
      stopWatchTime3 = stopWatchGet();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    
    void togglePinTwiceAndWriteBytes() {
      nrf_gpio_pin_toggle(OUTPUT_PIN);
      Serial.write(buffer, sizeof(buffer));
      Serial.flush();
      nrf_gpio_pin_toggle(OUTPUT_PIN);
    }
    
    void stopWatchInit() {
      CoreDebug->DEMCR |= 0x01000000; //Enable the use of DWT.
    }
    
    void stopWatchStart() {
      DWT->CYCCNT = 0; //Reset cycle counter.
      DWT->CTRL |= 0x1; //Enable cycle counter.
    }
    
    uint32_t stopWatchGet() {
      return DWT->CYCCNT; //Number of clock cycles stored in count variable.
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-28
      • 1970-01-01
      • 2022-07-14
      • 1970-01-01
      • 2022-07-03
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      相关资源
      最近更新 更多