Author Topic: SimbleeBLE affects TIMER1/2 accuracy  (Read 3991 times)

Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #15 on: May 19, 2016, 10:25:29 PM »
BTW, I'm using Arduino 1.6.5-r5 as per QuickStart guide.

Tim

tolson

  • Global Moderator
  • *****
  • Posts: 839
  • Karma: +19/-0
    • View Profile
    • Thomas Olson Consulting
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #16 on: May 20, 2016, 10:14:12 AM »
I think you nailed it. There i no reason to switch to RC mode to run BLE. Can you run that same comparison between using delay() vs ULPDelay too? With and without BLE?


Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #17 on: May 20, 2016, 11:20:03 AM »
Yes, I just heard from RF Digital and they've confirmed that the switch from Xtal to RC is the issue. Their team is working on it. Not sure if/when/how it will be resolved but hope to hear from them soon.

I can try the test you suggest Tolson, but not until later today.

At this point, I presume that a fix from RF Digital is required. No way for me/us implement a workaround?

Thanks.

CaryDubois

  • RFduino Newbie
  • *
  • Posts: 1
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #18 on: May 30, 2016, 12:50:53 AM »
Hello Everyone..i am new to this forum. I understand the precedent the BT radio has over code and interrupts, but my understanding was that by using peripherals with PPI they just do their thing on their own without relying on other system resources. Interestingly, I'm also using RTC1 (again, with PPI) and it appears to be solid and accurate when BLE is on.

pcb prototype
« Last Edit: June 22, 2016, 02:10:35 PM by CaryDubois »

bsiever

  • RFduino Full Member
  • ***
  • Posts: 89
  • Karma: +4/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #19 on: May 30, 2016, 05:21:41 AM »
CaryDubois,

That is pretty much true.  As long as all the activities are handled via the PPI mechanism there shouldn't be much of a difference between BLE and non-BLE modes.

Tim's problem occurred because he is using an underlying timer and the source of the timer clock changes between BLE and non-BLE modes.  Based on the evidence, it seems like the clocks are running at two different frequencies.  Tim probably could use a correction factor to determine the time appropriately for the two different modes if necessary, but this seems like it might be a fragile design choice.

  Bill

Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #20 on: May 30, 2016, 05:01:02 PM »
RTC1 works on LFCLK whereas TIMER1 and TIMER2 work on HFCLK. SimbleeBLE.start switches the clock source to RC rather than Xtal (external crystal).

I'm hoping for a fix from RF Digital so that after calling SimbleeBLE.start we can explicitly request the external crystal for HFCLK. This post at Nordic explains the issue clearly (see comments):

https://devzone.nordicsemi.com/question/79934/ble-affects-timer12-accuracy/

I will post here when I get official word from RF Digital.

Thanks,

Tim

Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #21 on: November 16, 2016, 12:48:49 PM »
This is a long-overdue post to this thread. Simblee library 1.1.0 included support for the following calls:

SimbleeBLE_requestHFCLK();
SimbleeBLE_checkHFCLK(&isRunning);

After calling SimbleeBLE.begin(), run this code to request the hi frequency clock:

  uint32_t isRunning = 0;
  SimbleeBLE_requestHFCLK();
  // Wait for the crystal to startup
  while (!isRunning) {
    SimbleeBLE_checkHFCLK(&isRunning);
  }

TIMER1 and TIMER2 then benefit from the increased accuracy of the HFCLK.

Tim

JeffNYC

  • RFduino Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #22 on: March 06, 2017, 01:59:27 PM »
Thanks for updating this post Tim.

I'm very new to this platform, and have no prior experience with RFDuino, so forgive my question if it's obvious.  But is there any reason the Timer code you provided would not work with SimbleeForMobile instead of SimbleeBLE? 

I'm trying to use your pulse measuring code (from your first post in this thread) to read an RC PWM signal (1.2-1.8ms pulse width), and it works great as a standalone program.  However, once I add it into a larger program that uses SimbleeForMobile, it stops working and so does SFM.

I've never used Timer-level code before, so perhaps (hopefully) there's a simple tweak to make it work with SimbleeForMobile instead of SimbleeBLE?

And if anyone could point me to some sort of tutorial / documentation on this type of language: NRF_GPIOTE->CONFIG..etc.. it would be a huge help.  I have no idea how to follow it and try to debug...

Thanks!

Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #23 on: March 12, 2017, 02:45:38 PM »
Hi Jeff,

It would be helpful to see some code, even if just the standalone code. I'm not experienced with PWM but I've learnt the whole PPI mechanism of Simblee/RFduino and happy to assist if that's where your issues lie.

Tim

JeffNYC

  • RFduino Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #24 on: March 15, 2017, 10:07:24 PM »
Thanks for the reply Tim - sorry I didn't see it until now.  I rebuilt the code again from an earlier version to be sure I hadn't made a mistake in adding in your Timer code, and lo and behold it worked beautifully the second time around.  I haven't been able to trace exactly what the relevant difference was, but it may have been related to interrupts...

When I first tried to include your code (and it didn't' work), I had several interrupt pins configured using [ attachPinInterrupt(pin, callback, mode); ] as described in the Simblee User Guide v2.06.   I removed these during the rebuild and when the code worked they were not yet added back.

Well, now I just added back one attachPinInterrupt() and SFM has stopped working again.  Literally if I comment out the attachPinInterrupt() line, everything is fine, but I uncomment it and SFM will no longer connect (or even show up in the 'Found Simblees' list).

I wonder if there is a conflict between the attachPinInterrupt() described in the User Guide and the dynamic_attachInterrupt(GPIOTE_IRQn, reportTime) included in your code?  And I can't seem to find any documentation on the use of dynamic_attachInterrupt() for Simblee anywhere, so I'm at a loss here...

I should also add that I'm using the loadWS2812() code from Tolsen to drive a few WS2812 LEDs, and that code includes a very short noInterrupts() loop.  But that has been playing nicely with SFM ever since I put in code to only call it every 100ms and only if the Simblee radio is not active:

if (!SimbleeForMobile.radioActive && ledUpdateRate < (loopTime - ledLastUpdated) ){
    loadWS2812();
}

Unfortunately, I don't have the ability to control when pin interrupts occur to ensure that the radio is not active and thus disrupted (like I do with loadWS2812()).  Perhaps this is where dynamic_attachInterrupt() comes into play?

Anyways, I'll stop speculating beyond my area of expertise and see what others have to say.  And I'll do my best to past all the other relevant code shortly in a subsequent post.

JeffNYC

  • RFduino Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #25 on: March 16, 2017, 12:38:53 AM »
One thing I should clarify regarding my SFM issues - after I upload the code, my phone usually sees the Simblee device initially, then after I try to connect, I get the error message:

'Connection Request to the Simblee timed out'

And after that I no longer see my device show up in the 'Found Simblees' list...

Here's the relevant code - I had to cut out a lot of unrelated code; hopefully I didn't miss anything relevant:



Code: [Select]
#include <SimbleeForMobile.h>

// Vars for Input RC throttle PWM Read
int throttlePin = 20;
float pwm_value = 0;
float pwm_value_store = 0;

// Vars for WS2812 LEDs
unsigned long ledUpdateRate = 100;  // in milliseconds - how often to update the LEDs, i.e call ws2812
unsigned long ledLastUpdated = 0;
const int ws2812pin = 15;
const int nPIXELS = 5;
const int nLEDs = nPIXELS * 3;
uint8_t ledBar[nLEDs];
#define LED_DELAY         0         // delay after calling ws2812

// Vars for Simblee UI
unsigned long simbleeMobileUpdateRate = 400; // in milliseconds
unsigned long lastUpdated = 0;
//Lots of unnecessary stuff here

// Vars for UART communication (using UART to control a peripheral device - wouldn't think this would interfere with things, but put it here just in case)
long serialSpeed = 230400; 

// Vars for external encoder (that requires external pin interrupts...)  A quadrature encoder but don't care about direction so only using one output.
#define EXTERNAL_ENCODER         14     // Digital pin for input from magnetic encoder
volatile int interruptCount = 0;
volatile boolean interruptCalled = false;  //  was interrupt called?
unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;
float speed = 1;


void setup() {

  override_uart_limit = true;
  Serial.begin(serialSpeed, 10, 13);  // RX, TX
  delay(1000);
 
  pinMode(throttlePin,INPUT);

 // Configure TIMER1 as timer
  NRF_TIMER1->TASKS_STOP = 1; // Stop timer
  NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;  // Set to timer mode
  NRF_TIMER1->PRESCALER = 6;  // overflow is every 2.097152 seconds - modified this to get sufficient resolution
  NRF_TIMER1->TASKS_CLEAR = 1;  // Clear the timer
  NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit;  // Set to 16 bit
 
  // Configure GPIOTE channel 0 as event that occurs when throttlePin changes from digital
  // low to high.
  NRF_GPIOTE->CONFIG[0] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
              | (throttlePin << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
 
  // Configure GPIOTE channel 1 as event that occurs when throttlePin changes from digital
  // high to low.
  NRF_GPIOTE->CONFIG[1] =  (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)
              | (throttlePin << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
 
  // Interrupt only on high to low.
  NRF_GPIOTE->INTENCLR = GPIOTE_INTENSET_IN0_Msk;
  NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk;
 
  // Clear all events.
  NRF_GPIOTE->EVENTS_IN[0] = 0;
  NRF_GPIOTE->EVENTS_IN[1] = 0;
  NRF_GPIOTE->EVENTS_IN[2] = 0;
  NRF_GPIOTE->EVENTS_IN[3] = 0;
 
  // Attach interrupt handler.
  dynamic_attachInterrupt(GPIOTE_IRQn, reportTime);
 
  // Configure PPI channel 0 to start TIMER1 on low to high.
  simblee_ppi_channel_assign(0, &NRF_GPIOTE->EVENTS_IN[0], &NRF_TIMER1->TASKS_START);
  // Configure PPI channel 1 to stop TIMER1 on high to low.
  simblee_ppi_channel_assign(1, &NRF_GPIOTE->EVENTS_IN[1], &NRF_TIMER1->TASKS_STOP);

// Setup for LEDs
  pinMode(ws2812pin, OUTPUT);
  digitalWrite(ws2812pin, LOW);
  // Initialize the ledBar array - all LEDs OFF.
  for(int wsOut = 0; wsOut < nLEDs; wsOut++){
    ledBar[wsOut] = 0x00;
  }
  loadWS2812();
  delay(1);

  pinMode (EXTERNAL_ENCODER, INPUT);

  SimbleeForMobile.deviceName = "Test";
  SimbleeForMobile.advertisementData = "Control";
  SimbleeForMobile.domain = "example.com";
  SimbleeForMobile.baseline = "Feb 22 2017 12:00:00";
  SimbleeForMobile.txPowerLevel = 0;              // txPowerLevel can be any multiple of 4 between -20 and +4, inclusive. +4 is >100ft, -20 is a few feet
  SimbleeForMobile.begin();

  attachPinInterrupt(EXTERNAL_ENCODER, encoderPulseCount, HIGH);     //  This is the declaration that causes SFM to no longer respond.  Comment this out and SFM functions normally.
  delay(1000);
 
}

void loop() {

  SimbleeForMobile.process();
  delay(1);

// Simblee Update Code
  unsigned long loopTime = millis();
  if (SimbleeForMobile.updatable && simbleeMobileUpdateRate < (loopTime - lastUpdated) ){
    lastUpdated = loopTime;
    // Lots of Simblee UI stuff here
  }

//  Update encoder values
  if (interruptCalled) {
    currTime = micros();
    timeDiff = currTime - lastTime;
    speed = (float)interruptCount * 1000000.0 / (float)timeDiff;   // Calculate RPM from time difference and interrupt count (pulses)
    interruptCalled = false;
    interruptCount = 0;
    lastTime = currTime;
  }

// Read Throttle Signal and Convert to Throttle / Brake for VESC
  pwm_value = pwm_value_store / 2.5;    // Do relevant math here to convert pulse width to throttle value

// Update WS2812 if enough time elapsed and Simblee radio not on
if (!SimbleeForMobile.radioActive && ledUpdateRate < (loopTime - ledLastUpdated) ){
    loadWS2812();
    delay(LED_DELAY);
    ledLastUpdated = loopTime;
  }
 
}

void reportTime(void) {
 
  if (NRF_GPIOTE->EVENTS_IN[1] != 0) {

    // clear event
    NRF_GPIOTE->EVENTS_IN[1] = 0;

    // timer has been stopped, capture value
    NRF_TIMER1->TASKS_CAPTURE[0] = 1;

    // get timer value
    unsigned long timerValue = NRF_TIMER1->CC[0];

    // clear timer for next cycle
    NRF_TIMER1->TASKS_CLEAR = 1;
   
    pwm_value_store = timerValue;
   
  }
}

void loadWS2812(){      // update LEDs using Tolsen's code - doubled NOPs as wasn't working in original form with the WS2812's I had
  noInterrupts();
  for(int wsOut = 0; wsOut < nLEDs; wsOut++){
    for(int x=7; x>=0; x--){
      NRF_GPIO->OUTSET = (1UL << ws2812pin);
      if(ledBar[wsOut] & (0x01 << x)) {
        __ASM ( \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              );
        NRF_GPIO->OUTCLR = (1UL << ws2812pin);
      }else{
        NRF_GPIO->OUTCLR = (1UL << ws2812pin);
        __ASM ( \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              );     
      }
    }
  }
  delayMicroseconds(50); // latch and reset WS2812
  interrupts();
}

int encoderPulseCount (uint32_t dummyPin) {   // ISR for encoder pulse read - dummyPin required...
  interruptCount++;
  interruptCalled = true;
  return 0;                               // ...as is 'return 0'
}

void ui(){
// Lots of stuff here
}

void ui_event(event_t &event) {
// Lots of stuff here
}


Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #26 on: March 17, 2017, 09:58:08 PM »
Hi Jeff,

With this line:

attachPinInterrupt(EXTERNAL_ENCODER, encoderPulseCount, HIGH);

I presume you want encoderPulseCount to be called when pin EXTERNAL_ENCODER goes HIGH, correct? I don't know why it's causing SFM to break.

You could try using GPIOTE to fire the GPIOTE_IRQn interrupt routine for both throttlePin going high to low and EXTERNAL_ENCODER going low to high. You would add this code:

Code: [Select]
NRF_GPIOTE->CONFIG[2] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
              | (EXTERNAL_ENCODER << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN2_Msk;

Then instead of having two interrupt routines (reportTime and encoderPulseCount), you would have a single interrupt routine that would test which interrupt has fired and then call either reportTime or encoderPulseCount.

Code: [Select]
if (NRF_GPIOTE->EVENTS_IN[1] == 1) {
   NRF_GPIOTE->EVENTS_IN[1] = 0;
   reportTime();
} else if (NRF_GPIOTE->EVENTS_IN[2] == 1) {
   NRF_GPIOTE->EVENTS_IN[2] = 0;
   encoderPulseCount();
}

With my limited experience, this is what I would try.

Maybe others will have suggestions.

Tim

JeffNYC

  • RFduino Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #27 on: March 18, 2017, 09:58:16 PM »
Hi Tim,

Thanks so much for taking the time to share this solution!  I didn't initially realize that the 'GPIOTE' and dynamic_attachInterrupt language could be used for multiple ISR's running in the same branch.
I tried an approach like you described below and was quite relieved to see that it worked!  I used slightly different language than what you wrote (as I'd copied an encoder example I found yesterday), and I didn't split the reportTime and encoder pulse stuff into two separate functions, but it seems to work well as-is.  Here's the relevant code that I have that now works:

Code: [Select]
NRF_GPIOTE->CONFIG[2] =  (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
              | (EXTERNAL_ENCODER << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN2_Set << GPIOTE_INTENSET_IN2_Pos;
I'm not exactly sure how the differences in the last line impact operation.  Can you explain?

and here's the expanded reportTime ISR:

Code: [Select]
void reportTime(void) {
 
  if (NRF_GPIOTE->EVENTS_IN[1] != 0) {

    // clear event
    NRF_GPIOTE->EVENTS_IN[1] = 0;

    // timer has been stopped, capture value
    NRF_TIMER1->TASKS_CAPTURE[0] = 1;

    // get timer value
    unsigned long timerValue = NRF_TIMER1->CC[0];

    // clear timer for next cycle
    NRF_TIMER1->TASKS_CLEAR = 1;
   
    pwm_value_store = timerValue;
   
  }
 
  if( (NRF_GPIOTE->EVENTS_IN[2] == 1) && (NRF_GPIOTE->INTENSET & GPIOTE_INTENSET_IN2_Msk))  {
    NRF_GPIOTE->EVENTS_IN[2] = 0;
    interruptCount++;
    interruptCalled = true;
  }
 
}



And here's the full (edited-down) code in one piece in case that's helpful to anyone:

Code: [Select]
#include <SimbleeForMobile.h>

// Vars for Input RC throttle PWM Read
int throttlePin = 20;
float pwm_value = 0;
volatile float pwm_value_store = 0;

// Vars for WS2812 LEDs
unsigned long ledUpdateRate = 100;  // in milliseconds - how often to update the LEDs, i.e call ws2812
unsigned long ledLastUpdated = 0;
const int ws2812pin = 15;
const int nPIXELS = 5;
const int nLEDs = nPIXELS * 3;
uint8_t ledBar[nLEDs];
#define LED_DELAY         0         // delay after calling ws2812

// Vars for Simblee UI
unsigned long simbleeMobileUpdateRate = 400; // in milliseconds
unsigned long lastUpdated = 0;
//Lots of unnecessary stuff here

// Vars for UART communication (using UART to control a peripheral device - wouldn't think this would interfere with things, but put it here just in case)
long serialSpeed = 230400;

// Vars for external encoder (that requires external pin interrupts...)  A quadrature encoder but don't care about direction so only using one output.
#define EXTERNAL_ENCODER         14     // Digital pin for input from magnetic encoder
volatile int interruptCount = 0;
volatile boolean interruptCalled = false;  //  was interrupt called?
unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;
float eSpeed = 1;


void setup() {

  override_uart_limit = true;
  Serial.begin(serialSpeed, 10, 13);  // RX, TX
  delay(1000);
 
  pinMode(throttlePin,INPUT);

 // Configure TIMER1 as timer
  NRF_TIMER1->TASKS_STOP = 1; // Stop timer
  NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer;  // Set to timer mode
  NRF_TIMER1->PRESCALER = 6;  // overflow is every 2.097152 seconds - modified this to get sufficient resolution
  NRF_TIMER1->TASKS_CLEAR = 1;  // Clear the timer
  NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit;  // Set to 16 bit
 
  // Configure GPIOTE channel 0 as event that occurs when throttlePin changes from digital
  // low to high.
  NRF_GPIOTE->CONFIG[0] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
              | (throttlePin << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
 
  // Configure GPIOTE channel 1 as event that occurs when throttlePin changes from digital
  // high to low.
  NRF_GPIOTE->CONFIG[1] =  (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)
              | (throttlePin << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
 
  //  Configure GPIOTE channel 2 (encoder) as event that occurs when encoder toggles from digital
  // high to low or vice versa
  NRF_GPIOTE->CONFIG[2] =  (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos)
              | (EXTERNAL_ENCODER << GPIOTE_CONFIG_PSEL_Pos)
              | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
 
  // Interrupt only on high to low (or toggle?)
  NRF_GPIOTE->INTENCLR = GPIOTE_INTENSET_IN0_Msk;
  NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk;
  NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN2_Set << GPIOTE_INTENSET_IN2_Pos;

  // Clear all events.
  NRF_GPIOTE->EVENTS_IN[0] = 0;
  NRF_GPIOTE->EVENTS_IN[1] = 0;
  NRF_GPIOTE->EVENTS_IN[2] = 0;
  NRF_GPIOTE->EVENTS_IN[3] = 0;
 
  // Attach interrupt handler.
  dynamic_attachInterrupt(GPIOTE_IRQn, reportTime);
 
  // Configure PPI channel 0 to start TIMER1 on low to high.
  simblee_ppi_channel_assign(0, &NRF_GPIOTE->EVENTS_IN[0], &NRF_TIMER1->TASKS_START);
  // Configure PPI channel 1 to stop TIMER1 on high to low.
  simblee_ppi_channel_assign(1, &NRF_GPIOTE->EVENTS_IN[1], &NRF_TIMER1->TASKS_STOP);

// Setup for LEDs
  pinMode(ws2812pin, OUTPUT);
  digitalWrite(ws2812pin, LOW);
  // Initialize the ledBar array - all LEDs OFF.
  for(int wsOut = 0; wsOut < nLEDs; wsOut++){
    ledBar[wsOut] = 0x00;
  }
  loadWS2812();
  delay(1);

  pinMode (EXTERNAL_ENCODER, INPUT);

  SimbleeForMobile.deviceName = "Test";
  SimbleeForMobile.advertisementData = "Control";
  SimbleeForMobile.domain = "example.com";
  SimbleeForMobile.baseline = "Feb 22 2017 12:00:00";
  SimbleeForMobile.txPowerLevel = 0;              // txPowerLevel can be any multiple of 4 between -20 and +4, inclusive. +4 is >100ft, -20 is a few feet
  SimbleeForMobile.begin();

  delay(1000);
 
}

void loop() {

  SimbleeForMobile.process();
  delay(1);

// Simblee Update Code
  unsigned long loopTime = millis();
  if (SimbleeForMobile.updatable && simbleeMobileUpdateRate < (loopTime - lastUpdated) ){
    lastUpdated = loopTime;
    // Lots of Simblee UI stuff here
  }

//  Update encoder values
  if (interruptCalled) {
    currTime = micros();
    timeDiff = currTime - lastTime;
    eSpeed = (float)interruptCount * (1000000.0 / 6.0) / (float)timeDiff;   // Calculate RPM from time difference and interrupt count (6 pulses/rev)
    interruptCalled = false;
    interruptCount = 0;
    lastTime = currTime;
  }

// Read Throttle Signal and Convert to Throttle / Brake for VESC
  pwm_value = pwm_value_store / 2.5;    // Do relevant math here to convert pulse width to throttle value

// Update WS2812 if enough time elapsed and Simblee radio not on
if (!SimbleeForMobile.radioActive && ledUpdateRate < (loopTime - ledLastUpdated) ){
    loadWS2812();
    delay(LED_DELAY);
    ledLastUpdated = loopTime;
  }
 
}

void reportTime(void) {
 
  if (NRF_GPIOTE->EVENTS_IN[1] != 0) {

    // clear event
    NRF_GPIOTE->EVENTS_IN[1] = 0;

    // timer has been stopped, capture value
    NRF_TIMER1->TASKS_CAPTURE[0] = 1;

    // get timer value
    unsigned long timerValue = NRF_TIMER1->CC[0];

    // clear timer for next cycle
    NRF_TIMER1->TASKS_CLEAR = 1;
   
    pwm_value_store = timerValue;
   
  }
 
  if( (NRF_GPIOTE->EVENTS_IN[2] == 1) && (NRF_GPIOTE->INTENSET & GPIOTE_INTENSET_IN2_Msk))  {
    NRF_GPIOTE->EVENTS_IN[2] = 0;
    interruptCount++;
    interruptCalled = true;
  }
 
}

void loadWS2812(){      // update LEDs using Tolsen's code - doubled NOPs as wasn't working in original form with the WS2812's I had
  noInterrupts();
  for(int wsOut = 0; wsOut < nLEDs; wsOut++){
    for(int x=7; x>=0; x--){
      NRF_GPIO->OUTSET = (1UL << ws2812pin);
      if(ledBar[wsOut] & (0x01 << x)) {
        __ASM ( \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              );
        NRF_GPIO->OUTCLR = (1UL << ws2812pin);
      }else{
        NRF_GPIO->OUTCLR = (1UL << ws2812pin);
        __ASM ( \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              " NOP\n\t" \
              );     
      }
    }
  }
  delayMicroseconds(50); // latch and reset WS2812
  interrupts();
}

void ui(){
// Lots of stuff here
}

void ui_event(event_t &event) {
// Lots of stuff here
}



Anyways, I'm thrilled to have gotten this working, although I wish I knew even a little about what the GPIOTE code above means.  It's clearly not 'newbie-friendly' Arduino ;)

Interestingly, I'd emailed RFDuino Support about this problem prior to discovering the solution, and their response was that a single Simblee will not be able to handle encoder interrupts as well as running BLE and other code, and that my solution would be to add a 2nd Simblee module to handle interrupts and code, and communicating with the first via SimbleeCOM.  This is not a solution that I would like to have to employ!  I shared the functioning code with them and am waiting to hear back whether this is indeed a satisfactory and stable solution.

The only last remaining hiccup is that the data from the encoder is a little bit erratic.  I'm wondering if this is due to the BLE radio interrupts affecting the pulse timing measurements?  Does the internal micros() clock stop counting when the BLE radio activates, rendering some pulse interval measurements to come out shorter than they actually are?   This would be consistent with what I'm observing:  small, irregular errors in RPM measurement that are always higher than they should be (i.e. the pulse time measurements are shorter).  Wondering if anyone has any more insight into this and whether there's a solution?

Thanks again for all the help Tim!!


Tim

  • RFduino Sr. Member
  • ****
  • Posts: 119
  • Karma: +2/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #28 on: March 19, 2017, 08:38:05 PM »
Hi Jeff,

Yes, micros() is inaccurate when BLE is on due to priority of the BLE stack. That's what led me to use PPI, GPIOTE and TIMERs because they are not subject to CPU or BLE. Not sure about delayMicroseconds(), but I presume same issue. micros() is just reading the value of RTC1, so if you could use PPI and GPIOTE to start and stop RTC1 and read it's value in an interrupt routine, that might do the trick. I don't have much experience with encoders, so I might be way off here.

I'm very interested to hear RFDigital's response. If you're using PPI, GPIOTE and TIMERs to "handle encoder interrupts," my understanding is it should work.

Thanks for adding to the conversation.

Tim

JeffNYC

  • RFduino Newbie
  • *
  • Posts: 7
  • Karma: +0/-0
    • View Profile
Re: SimbleeBLE affects TIMER1/2 accuracy
« Reply #29 on: March 28, 2017, 09:40:35 PM »
Just an update - I did hear back from RF Digital support and good news: they fully endorse this approach, and their prior recommendation to use 2 Simblees was only based on what would be the simplest implementation - here's the essence of what they wrote:

"The code you're using, directly modifying NRF registers and using PPI to communicate directly between peripherals, is much higher-level than most of our customers use. Most of our customers use our libraries in Arduino to program their Simblee. I realize you are still using Arduino, but the code you've used here will offer you an increase in speed.

Having looked at everything, I do think that this code is good to go, and will remain stable. You should still be able to add another encoder without detriment."

So it looks like you've created quite a fantastic solution here Tim!  Thank you very much for sharing this with the community, as it has really saved the day for me, and I'm sure will do the same for many others.

-Jeff