Author Topic: Measure Signal Frequency  (Read 13085 times)

PopePepe

  • Newbie
  • *
  • Posts: 7
Measure Signal Frequency
« on: March 22, 2013, 03:04:16 am »
Hi

I want(ed) to use my digispark to measure a signal frequency (rising edges) and communicate via Serial.
The frequency is low (1 to 200 Hz).

Do I get it right, i cannot use DigiUSB-Communication (whatever-this-is-thingy) and INT0 (Pin 2) ?
Is it possible to use the standard Serial.write() with RX (Pin 1) and TX (Pin 0) and an external usb-serial-converter?

I admit, I never read the specs (I just told my buddy I'll take one, whatever it is  ::) ). Until now I thought this would be a very simple task.

Is there another external interrupt line left?

Besides the fact that I probably cant (because of INT0), what if I want to communicate with between my pc (Mac or Debian/Ubuntu) and the digispark. What do i have I have to do? What kind of usb-libs are needed, are there any examples? (I'm talking about the PC-Side (C/C++/Python Programm))
Maybe there's a fancy workaround pssible ?
 

semicolo

  • Full Member
  • ***
  • Posts: 137
Re: Measure Signal Frequency
« Reply #1 on: March 22, 2013, 06:03:05 am »
You could use the pin change interrupt to be able to still use INT0.
Look for the libusb examples if you want to go the USB route, else you could use the software serial library.

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Measure Signal Frequency
« Reply #2 on: March 22, 2013, 10:01:35 am »
The C++ examples are included in the download as well as command line programs.


The python examples are here: https://github.com/digistump/DigisparkExamplePrograms

Bluebie

  • Sr. Member
  • ****
  • Posts: 486
Re: Measure Signal Frequency
« Reply #3 on: March 22, 2013, 05:32:56 pm »
Using INT0 with USB libraries is tricky and something you need to be quite careful with. In previous versions of the digispark IDE, it wasn't possible. As of the 1.0.4 release a couple of days ago, INT0 interrupts don't crash the USB library so long as the interrupt happens infrequently. 200hz should be infrequent enough. Effectively the problem is this:


To allow the USB libraries to work with timers (millis, micros, delay) and INT0, I changed the interrupt behaviour on all of these to allow stacking. That is, the millis() interrupt can fire to increment the time, and while it's doing that the PCINT0 interrupt used by DigiUSB can fire and will immediately pause the millis interrupt and jump in to the USB library. In previous versions of the digispark software the USB library would have to wait till millis was done, and computers aren't that patient so they would disconnect the digispark and send an error to the program accessing it. Now there is just one issue. When an interrupt first fires it takes a couple of instructions before interrupts are re-enabled so pin change interrupt used by USB can fire again. This tiny delay is okay if it only happens once, but if it happens several times the delay builds up to be so long that again, the computer disconnects the digispark. That's fine for timers and other interrupts, but INT0 is higher priority than PCINT0, so if the INT0 interrupt is enabled and the pin is changing very quickly (in the megahertz range) it still stalls the chip and looses the USB connection. Worse yet, with the new behaviour the interrupt keeps adding function calls to the stack, so the chip quickly runs out of memory and crashes the program on the digispark!


So for all these reasons INT0 must be used with great caution, especially when using the USB libraries. Don't use INT0 with buttons which need software debouncing - use hardware debouncing in this case (capacitor and resistor low pass filter). If the frequency you're measuring is cleanly digital it should be okay because those frequencies are not anywhere near high enough to cause issue. If they maybe unclean, you should use a small low pass filter just like with hardware button debouncing - pass your signal through a resistor to pin 2, and connect pin2 to ground with a capacitor. Tweaking the resistor value relative to the capacitor will adjust how high of a frequency you can read.


As for how to measure it, just add a global variable of the type unsigned long, and in your interrupt handler write down the time and how long it's been since the last time the interrupt fired. Then it's easy to calculate in a function later on:


Code: [Select]

#include <DigiUSB.h>


unsigned long time_of_last_interrupt;
unsigned long duration_of_cycle;
void interrupt() {
  unsigned long time_now = micros();
  duration_of_cycle = time_now - time_of_last_interrupt;
  time_of_last_interrupt = time_now;
}


unsigned int d2_frequency() {
  // should be 1000000UL, working around bug, micros thinking
  // clock speed is 16.0mhz when it is 16.5mhz
  return 1031250UL / duration_of_cycle;
}


void setup() {
  DigiUSB.begin();
  attachInterrupt(0, interrupt, FALLING);
}


void loop() {
  DigiUSB.println(d2_frequency());
  DigiUSB.delay(200); // update five times per second
}


This version works really nicely up to frequencies of about 330hz, after that it starts getting off by 1hz or so in some of the measurements, just due to the granularity of micros I think. Or maybe my signal generator isn't working right. Either way it seems good for your specs. Notably the number in d2_frequency, 1031250UL, should really be 1000000UL, but there seems to be a bug in micros() when compiling for speeds other than 16.0mhz and 8mhz with current versions of the digispark software. millis doesn't seem to have the issue, but effectively micros thinks the clock speed is 16.0mhz when it is 16.5mhz, so I have corrected for this by multiplying 1000000 with 1.03125, because 16.5/16 = 1.03125. Seems to work alright but when this bug gets fixed in a future version of the digispark software you'll need to change that number in your code back to 1000000UL.


You can change the 200ms delay in loop() to be much lower if you want more frequent updates. I think 10ms would be fine for an update frequency of 100hz. I don't know how much lower/faster it would work. Depends partially on how feircely your host software is polling the device. You could squeeze out a little extra performance by changing the println type to send out raw bytes as well, instead of decimal ascii.
« Last Edit: March 22, 2013, 05:39:13 pm by Bluebie »

PopePepe

  • Newbie
  • *
  • Posts: 7
Re: Measure Signal Frequency
« Reply #4 on: March 24, 2013, 05:15:46 am »
The C++ examples are included in the download as well as command line programs.

The python examples are here: https://github.com/digistump/DigisparkExamplePrograms
Thanks, I had the previous Ardunio version (1.0.3) without commmand line tools. Now it's more clear to me .

And thanks to Bluebie. You did not only bring a great explanation, you also solved my problem :D (Speaking od the arduini part  ;) )
Anyway I am having issues with the USB-Comunication:
I am usind this code and the commandline monitor "digiusb":

Code: [Select]
#include <DigiUSB.h>
void setup() {
  DigiUSB.begin();
}

unsigned int a = 0;
void loop() {
  DigiUSB.println(a++);
  DigiUSB.delay(1000);
}

On my Mac the code works untill "a" reaches 12, then nothing else is transmitted. (It seems like "digiusb" doesnt receive more than 12 values in general).

Since I want to use the digispark with my raspberry pi I downloaded the Linux version. I had to compile the commandline tools (and previously install libncurses5-dev and libusb-dev) since the downloaded ones did not run. "cannot execute binary file" als user and "1: ./receive: Syntax error: word unexpected (expecting ")")" as root. I guess I got the udev-rules right.
After compling "digiusb" returns this and breaks: (No segmentation fault if run as root but not working anyway).
Code: [Select]
>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????Segmentation fault
After I was done typing here I realized I should give "receive" a chance. Surprisingly it does work and return some of the latest transmitted values (but not all, there seems to be some kind of buffer?). Only if I exectute "receive" frequently enough so that not to many values pile up he seems to get them all.
I guess I can live with this but its not that great. I actually wanted to use the transmitted values as a relative time reference ("the frequency is now ... Hz" and map it to another measurement going on at the same time). The expected jitter from the USB- or Serial Communication is fine since I want to measure "for a few seconds" with constant frequency.

I will give Windows a try ... but I'm not too happy with the whole USB-Story.

Bluebie

  • Sr. Member
  • ****
  • Posts: 486
Re: Measure Signal Frequency
« Reply #5 on: March 24, 2013, 08:22:22 am »
The digispark only has 512 bytes of memory, so the DigiUSB library doesn't waste very much of that by creating huge buffers to store your outgoing messages. If your program keeps writing new ones and the host computer isn't reading them in, eventually the program runs out of memory and starts writing over the previous bits in the buffer. I think it's the right trade off. If you need something fancier, you'll need to pay a little more. The Teensy devices have quite a bit more memory for instance, and have real spec-complaint USB ports, making them a tad more reliable, though the digispark seems reliable for most users.

PopePepe

  • Newbie
  • *
  • Posts: 7
Re: Measure Signal Frequency
« Reply #6 on: March 24, 2013, 10:54:47 am »
I did not mean to criticize or offend in general.  :D

Right now I just don't get why the monitor aka "digiusb" just doesn't receive more than 12 lines since it seems (I guess) to be constantly polling for something to receive. What's the difference to manually poll by frequently using the "receive" tool?

Bluebie

  • Sr. Member
  • ****
  • Posts: 486
Re: Measure Signal Frequency
« Reply #7 on: March 24, 2013, 05:15:54 pm »
I don't know anything about how the monitor tools digistump provided work. I've not used them much, because they aren't as good as my ruby ones yet. I don't know about how reliable they are, but I've been testing my ruby ones for more than a month now and have had no issue. My household doorbell depends on it so I'm pretty confident! If the digiusb rubygem wasn't working reliably, I could miss getting packages delivered! Mainly I haven't paid attention to the digistump ones yet because they just don't have many features implemented, like directional text colouring.


Maybe digistump has some answers?

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Measure Signal Frequency
« Reply #8 on: March 24, 2013, 05:43:17 pm »
I can confirm the bug in the monitor program - I'll give it a look tomorrow - this is the first release of these tools, so there certainly is room for improvement.


You can also find Pyhton examples here - https://github.com/digistump/DigisparkExamplePrograms


In addition, the Ruby gem is a great too, unless your on windows.


One trick I use given the limited RAM is to only send a value when a value is received - so I might use send to send a "1" (or anything) and then have the Digispark respond with its current reading - this makes sure you get the freshest data no matter the time between polling. See the DigiScope example sketch for an example of this.

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Measure Signal Frequency
« Reply #9 on: March 25, 2013, 01:43:43 am »
The monitor bug was actually just in the window scrolling - so it was an easy fix - updated file attached, I'll update the official release binaries tomorrow as well.

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Measure Signal Frequency
« Reply #10 on: March 26, 2013, 01:59:14 am »
The monitor bug is now fixed in all releases.

PopePepe

  • Newbie
  • *
  • Posts: 7
Re: Measure Signal Frequency
« Reply #11 on: March 26, 2013, 02:03:23 pm »
Nice job, thanks!
I did not realize the tool was just released, so I didn't expect a bug  :)

PopePepe

  • Newbie
  • *
  • Posts: 7
Re: Measure Signal Frequency
« Reply #12 on: March 28, 2013, 01:37:52 am »
Hey. Just a quick update on the actual topic:
I modified Bluebies code a bit to return the median of the last N (here 11) saved values. I'm using the median since I expect my signal to be a bit noisy (random peaks which would lead to a wrong time measurement). The median is a good filter to exclude wrong (too high or too low) values of a sequence. Using the mean value is also a possible solution but remeber this problem:
Sequence: 2 0 1 2 2 2 3 100 2 2 2
Mean: well ... not even close to 2
Median: 2 ... great!

This is just a quick Idea, coded on my way to work. So don't blame me  :D

The following code works for me up tp ca. 250Hz (exactly) and with a random offset until ca. 400Hz. Going higher leads to more random results.
Later I'll also use a hardware lowpass filter (break frequency ca. 200Hz. Checked my requeirements, I'll only need to measure up to 150Hz).
Please not that I manually removed the standard offset of 1Hz. I'm not sure where and why it comes from, I would have to use my brain to figure it out. Maybe this is a device and environment (temperature) specific problem. And I also don't know what's going on "behind the curtains" of the arduino-framework and the USB-Communication.

Btw: please note that theres a bug in this software. It does not work for 0Hz  ;) :D ;D !


Code: [Select]
#include <DigiUSB.h>
#define BUFFERSIZE 11 //Use odd buffersize for simple median calculation
#define MID (BUFFERSIZE - 1 ) / 2

unsigned long           duration_of_cycle_buffer[BUFFERSIZE];    // buffers last BUFFERSIZE period durations
//unsigned char read_pos  = 0;    // read position of ringbuffer
unsigned char write_pos = 0;    // write position of ringbuffer

char lock = 0;   // Use Mutex while median is beeing calculated

unsigned long time_of_last_interrupt;
unsigned long duration_of_cycle;

void interrupt() {
  unsigned long time_now = micros();
  duration_of_cycle = time_now - time_of_last_interrupt;

  if(!lock){
    //Write to Buffer
    duration_of_cycle_buffer[write_pos] = duration_of_cycle;
    if(write_pos++ >= BUFFERSIZE){
      write_pos = 0;
    }
  }
  time_of_last_interrupt = time_now;
}

// read from buffer and immediately calculate mean, median or sth similar

unsigned int read_from_buffer(){
  /*
  // mean
  //unsigned long mean = 0;
  double mean = 0;          //I never know If a double should be used on an AVR (Arduino) or nit
  for(char i = 0; i < BUFFERSIZE; i++){
    mean += duration_of_cycle_buffer[i];
  }
  mean /= BUFFERSIZE; */

  unsigned long tmp = 0;
  lock = 1;
  for(int i = 0; i < BUFFERSIZE - 1; i++){
      for(int j = 0; j < BUFFERSIZE - i - 1; j++){
        if(duration_of_cycle_buffer[j] > duration_of_cycle_buffer[j + 1]){
          tmp = duration_of_cycle_buffer[j];
          duration_of_cycle_buffer[j] = duration_of_cycle_buffer[j + 1];
          duration_of_cycle_buffer[j + 1] = tmp;
        }
      }
    }
  lock = 0;

  tmp = duration_of_cycle_buffer[MID];
 
  // should be 1000000UL, working around bug, micros thinking
  // clock speed is 16.0mhz when it is 16.5mhz
  //return 1031250UL / mean;
  return (1031250UL / tmp) + 1;//hack!
  // measured freequency always seems to be offset by 1Hz
  // this is not the correct way to work around, but ok for my needs
}

void setup() { 
  DigiUSB.begin();
  attachInterrupt(0, interrupt, RISING); // attach INT0 interrupt
}

void loop() {
  DigiUSB.println(read_from_buffer());
  DigiUSB.delay(500); // update
}
« Last Edit: March 28, 2013, 01:41:25 am by PopePepe »