This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Change in GPIO to start/stop timer and fire interrupt

Hello all,

This is my first post to the developer zone.

I'm creating a product using RFduino, which uses the nRF51822 chip for Bluetooth LE. (See www.rfduino.com/.../rfduino.datasheet.pdf )

I'm an iOS developer with some legacy assembler experience but no experience programming the nRF51822 chip, so I apologize for the simple questions but would appreciate some guidance in finding a solution or at least where to find the documentation that would help me learn.

The product I'm developing measures the speed of a moving object and sends the result to an iOS app via Bluetooth LE for display and analysis . The object is 11 inches in diameter and it passes through a laser beam that is aimed at a phototransistor that is connected to a GPIO pin on the RFduino. I need to be able to accurately measure the time that the object breaks the beam and then calculate the speed as the distance travelled (the diameter of the object, 11 inches) divided by the time (the duration the beam was broken).

My current implementation is not accurate and, therefore, unacceptable. The RFduino provides the Arduino IDE for programming the chip. My loop routine basically watches for the phototransistor GPIO analog value to drop below a threshold value (i.e. laser beam is broken), saves the current time when it does, then watches for the value to rise above the threshold value (i.e. laser beam restored) and, again, save the current time. The duration the beam was broken is calculated as endTime - startTime and the speed is then calculated as 11 inches divided by the duration. Typical durations range from 75-250 milliseconds. My code looks something like this:

void loop() {
	
	currentPhototransistorValue = analogRead(photoResistorPin);
	
	if (currentPhototransistorValue < thresholdValue && beamCurrentlyOn == 1) {
		
		startTime = millis();
		beamCurrentlyOn = 0;

	} else if (currentPhototransistorValue > thresholdValue && beamCurrentlyOn == 0) {
		
		endTime = millis();
		beamCurrentlyOn = 1;
		
		unsigned long duration = endTime - startTime;
		
		float speed = 11.0 / duration;

		// Send speed to iOS app ...

	}

}

The problem is that the duration, as measured by the above method, is not accurate because the BT radio can pre-empt my loop and at random cause a 5 ms or so hiccup. This is unacceptable for my application.

I'm hopeful that a viable solution is to use timer2 of the chip to do the timing. I've looked at some examples here and on the RFduino forum that show how to use the timers, but they all just program the timer to fire at a precise interval. For example, the code below calls the interrupt routine TIMER2_Interrupt every 500 milliseconds.

  NRF_TIMER2->TASKS_STOP = 1;	// Stop timer
  NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;  // taken from Nordic dev zone
  NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
  NRF_TIMER2->PRESCALER = 9;	// 32us resolution
  NRF_TIMER2->TASKS_CLEAR = 1; // Clear timer
  // With 32 us ticks, we need to multiply by 31.25 to get milliseconds
  NRF_TIMER2->CC[0] = 500 * 31; // 500 is milliseconds to fire interrupt
  NRF_TIMER2->CC[0] += 500 / 4;  // 500 is milliseconds to fire interrupt
  NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;  // taken from Nordic dev zone
  NRF_TIMER2->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos);
  attachInterrupt(TIMER2_IRQn, TIMER2_Interrupt);    // also used in variant.cpp to configure the RTC1 
  NRF_TIMER2->TASKS_START = 1;	// Start TIMER

So I'm trying to determine whether or not the following is possible, and if it is, if it will be more accurate than my current implementation.

I need to program timer2 to start when the phototransistor GPIO pin value transitions down past a threshold value, then stop when the pin value transitions up past the threshold value. Then the code needs to call an interrupt in my sketch (like the above sample calls TIMER2_interrupt). Then my interrupt routine (TIMER2_interrupt) needs to read the timer value to get the duration that the beam was broken, then reset the timer to wait for the next transition down of the GPIO pin value.

If this is all possible, could I expect greater precision compared to my current approach? Plus/minus 1 ms for measuring the duration would be acceptable accuracy.

If this is possible, I need to learn how to:

  • start and stop a timer based on the change of the analog value of a GPIO pin
  • read the current timer value
  • reset the timer to wait for the next drop in GPIO pin value

I would be delighted if this is relatively simple and someone could just show me the code. Otherwise I'm happy to learn by reading the appropriate documentation. Note that I don't have the nRF51822 development kit so I don't have access to the resources (documentation and otherwise) that comes with the purchase of the kit.

Whew ... I hope this is a reasonable and clear question. I appreciate your understanding that I'm new to this.

Many thanks to anyone who can offer me some help.

Tim

  • That was a long question. First off you do have access to just about all the resources you need, click the SDK and Documentation link up there ^^^ and go to the Infocenter, there you have the nRF51 manual, the nRF51822 spec, the softdevice API, pretty much everything.

    Using timer interrupts probably isn't going to work, the softdevice can preempt you pretty randomly. This is better with the latest softdevices and the rev 3 chips which block the CPU for much shorter intervals, so if your timer code runs at high priority and that's about all you have, you may get enough service to poll your GPIO.

    Knowing about nothing about Arduino I'm guessing the analogRead() function does something slow every time as well.

    If you can use PPI instead of polling you can use an event to trigger the timer to start counting, and another event to stop it again, you interrupt on the second event and read the timer, see how far it counted, then reset it and do it again. PPI (it's in the manual) is a way of having one event trigger a task without using the CPU, so it's accurate.

    The problem then comes from the analog to digital piece, you really want a logic 0 or logic 1 from your sense circuit, then it would be easy to hook up a digital IO pin to start and stop the timer. Can you add a simple circuit to do that, convert your analog output into a 1/0?

    If not then the chip contains a low power comparator which will generate events on upward and downward crossings of a reference voltage. Those events can then be used to start and stop the timer. See the LPCOMP chapter in the manual. There are two things which come to mind about using that, one is that the normal analog digital converter must be disabled when using the LPCOMP and I have no idea how you do that with arduino code, and secondly the converter is fast, so your signal may bounce near the threshold giving multiple events each end. There's ways to deal with that if you get to that point, either in hardware or software.

    So try reading the PPI chapter and the LPCOMP chapter and the TIMER chapter see if you can see how to use two PPI channels to take the LPCOMP UP and DOWN events and trigger the TIMER START and STOP tasks.

  • Thanks RK. That's a great start for me.

    Tim

  • Hi ...

    I've been making good progress on this and will post full working source, but for now a simple question.

    The code below uses GPIOTE to call an interrupt when a pin transitions from LoToHi and also from HiToLo. I've tested using two methods, using the RGB button shield and using a phototransistor+comparator (to convert phototransistor to digital 0/1). For the latter, a laser is aimed at the phototransistor and so the code is generating interrupts when the beam is broken and restored.

    In both cases it works generally well except occasionally I get an extra interrupt call. I'll press the button and get a LoToHi interrupt call, then when I release the button I'll get an unexpected LoToHi interrupt call immediately followed by a HiToLo interrupt call. Same behaviour when using the phototransistor+comparator.

    Any issues with my code? Is it possible that signal bounce is causing the issue? I understand bounce can be an issue especially with mechanical switches (i.e. RGB button shield), but I thought not so with a phototransistor+comparator.

    Code is here:

    #include <RFduinoBLE.h>
    
    int myPin = 5;
    
    void setup() {
      Serial.begin(9600);
        
      pinMode(myPin, INPUT); // using GPIO 5 as input
      
      // Configure GPIOTE channel 0 as event that occurs when pin 5 changes from digital
      // hi to low.
      NRF_GPIOTE->CONFIG[0] =  (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)
                  | (myPin << GPIOTE_CONFIG_PSEL_Pos) // using GPIO 5 as input
                  | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
      
      // Configure GPIOTE channel 1 as event that occurs when pin 5 changes from digital
      // low to hi.
      NRF_GPIOTE->CONFIG[1] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
                  | (myPin << GPIOTE_CONFIG_PSEL_Pos) // using GPIO 5 as input
                  | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
      
      NRF_GPIOTE->INTENSET = 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;
      
      // Enable GPIOTE interrupts and attach my handler.
      NVIC_EnableIRQ(GPIOTE_IRQn);
      attachInterrupt(GPIOTE_IRQn, MY_IRQHandler);
      
    }
    
    void MY_IRQHandler(void) {
      
      // Check which event caused interrupt.
      if (NRF_GPIOTE->EVENTS_IN[0] == 1) {
        
        // Clear the event that caused the interrupt.
        NRF_GPIOTE->EVENTS_IN[0] = 0;
        
        Serial.println("interrupt called for HiToLow");
        
      } else if (NRF_GPIOTE->EVENTS_IN[1] == 1) {
        
        // Clear the event that caused the interrupt.
        NRF_GPIOTE->EVENTS_IN[1] = 0;
        
        Serial.println("interrupt called for LoToHi");
        
      }
      
      
    }
    
    void loop(){
      
    }
    

    Many thanks,

    Tim

  • Tried it. It worked well! Thanks for the effort. You saved me hours on almost the exact same question. Have you thought of making a similar post on the RFduino forum?

Related