https://shawnreeves.net//wiki/index.php?title=Frequency_meter_on_Arduino&feed=atom&action=historyFrequency meter on Arduino - Revision history2024-03-29T06:03:39ZRevision history for this page on the wikiMediaWiki 1.34.1https://shawnreeves.net//wiki/index.php?title=Frequency_meter_on_Arduino&diff=2717&oldid=prevShawn at 20:51, 23 May 20142014-05-23T20:51:00Z<p></p>
<a href="https://shawnreeves.net//wiki/index.php?title=Frequency_meter_on_Arduino&diff=2717&oldid=2647">Show changes</a>Shawnhttps://shawnreeves.net//wiki/index.php?title=Frequency_meter_on_Arduino&diff=2647&oldid=prevShawn: /* Arduino-based frequency meter */2014-03-26T22:24:04Z<p><span dir="auto"><span class="autocomment">Arduino-based frequency meter</span></span></p>
<table class="diff diff-contentalign-left" data-mw="interface">
<col class="diff-marker" />
<col class="diff-content" />
<col class="diff-marker" />
<col class="diff-content" />
<tr class="diff-title" lang="en">
<td colspan="2" style="background-color: #fff; color: #222; text-align: center;">← Older revision</td>
<td colspan="2" style="background-color: #fff; color: #222; text-align: center;">Revision as of 22:24, 26 March 2014</td>
</tr><tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>
<td colspan="2" class="diff-lineno">Line 1:</td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>==Arduino-based frequency meter==</div></td><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>==Arduino-based frequency meter==</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>The following program directs an Arduino or compatible micro-controller to time a signal (falling voltage) on an input pin and report the inverse of the time between previous signals as a frequency in Hz, in a range of 100.0Hz to 0.10Hz. Used with the circuit diagrammed in [[File:Frequency-meter-Arduino-on-a-board.pdf]].</div></td><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>The following program directs an Arduino or compatible micro-controller to time a signal (falling voltage) on an input pin and report the inverse of the time between previous signals as a frequency in Hz, in a range of 100.0Hz to 0.10Hz. Used with the circuit diagrammed in [[File:Frequency-meter-Arduino-on-a-board.pdf]].</div></td></tr>
<tr><td colspan="2"> </td><td class='diff-marker'>+</td><td style="color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div><ins style="font-weight: bold; text-decoration: none;"></ins></div></td></tr>
<tr><td colspan="2"> </td><td class='diff-marker'>+</td><td style="color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div><ins style="font-weight: bold; text-decoration: none;">For a discussion of the circuit and its function, please see [[frequency meter]].</ins></div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>===Code===</div></td><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div>===Code===</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div> /*</div></td><td class='diff-marker'> </td><td style="background-color: #f8f9fa; color: #222; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;"><div> /*</div></td></tr>
</table>Shawnhttps://shawnreeves.net//wiki/index.php?title=Frequency_meter_on_Arduino&diff=2646&oldid=prevShawn: Created page with "==Arduino-based frequency meter== The following program directs an Arduino or compatible micro-controller to time a signal (falling voltage) on an input pin and report the inv..."2014-03-26T22:22:48Z<p>Created page with "==Arduino-based frequency meter== The following program directs an Arduino or compatible micro-controller to time a signal (falling voltage) on an input pin and report the inv..."</p>
<p><b>New page</b></p><div>==Arduino-based frequency meter==<br />
The following program directs an Arduino or compatible micro-controller to time a signal (falling voltage) on an input pin and report the inverse of the time between previous signals as a frequency in Hz, in a range of 100.0Hz to 0.10Hz. Used with the circuit diagrammed in [[File:Frequency-meter-Arduino-on-a-board.pdf]].<br />
===Code===<br />
/*<br />
On timing:<br />
micros() returns the number of microseconds since the Arduino board began running the current program.<br />
micros() returns an unsigned long integer, so it overflows to zero after 4,294,967,295 µs.<br />
millis() is similar except it counts milliseconds.<br />
NOTA BENE:Both millis() and micros() clear interrupts while they read the timer, so use them sparingly!<br />
<br />
On LCD quirks:<br />
Clearing the display with the clear/home command takes a lot of time.<br />
We might consider leaving characters in memory and just overwriting them.<br />
--We'd just need to be sure to write spaces at the end if one printout might be shorter than the previous printout.<br />
<br />
On interrupt routine:<br />
attachInterrupt(interrupt, ISR, mode)<br />
interrupt: 0 means digital pin 2 and 1 means digital pin 3. (Mega2560, Leonardo, and Due boards offer it on more pins)<br />
ISR: routine to call when interrupt happens.<br />
mode: LOW, CHANGE, RISING, FALLING (DUE boards also allow HIGH)<br />
*/<br />
//Uncomment the following to allow serial debug messages. May interfere with timing?<br />
//#define serialDebug 1<br />
//Define NOP as an assembly language macro to delay one cycle. See http://forum.arduino.cc/index.php?topic=43333.0<br />
#define NOP __asm__ __volatile__ ("nop\n\t")<br />
// and then use it in code as follows<br />
//NOP; // delay 62.5ns on a 16MHz AtMega<br />
<br />
//HARDWARE PIN SELECTION:<br />
//The pins are selected to minimize spaghetti-crossing on the breadboard, and so are dependent on the microcontroller's physical config.<br />
const int switchPin = 3;//must be pin 2 or 3<br />
const int switchPinInt = 1;//for 328-based arduinos, 0 means digital I/O pin 2, and 1 means digital I/O pin 3<br />
//We're using home-made SPI instead of the standard library since our LCD (EA DOGM081) uses its own flavor of SPI<br />
//The following pins depend on how the DOGM081 and the mircro-controller are connected<br />
const int DATAP = 9; //to SI on the DOGM081<br />
const int CLKP = 10; //to CLK on the DOGM081<br />
const int chipSelectPin = 11; //to CSB on the DOGM081<br />
const int RSPin = 12; //to RS on the DOGM081<br />
<br />
//CONDITIONAL, CONSTANT PARAMETERS:<br />
const unsigned long debounceMicroSeconds = 100;//Our routine will ignore signals that come closer together than this amount of microseconds<br />
const unsigned long maxTime = 11000000;//The amount of microseconds before going into waiting mode.<br />
<br />
//<br />
//It's recommended all variables used in the interrupt service routine are considered volatile,<br />
// so not kept in multi-purpose registers but in dedicated memory slots:<br />
volatile unsigned long hitTime;<br />
unsigned long lastTime;//Track the time of the previous signal so we can calculate time elapsed since.<br />
unsigned long microsSince;//for determining idleness. 0 <= ul <= 4,294,967,295<br />
unsigned long periodMicroSeconds;<br />
float frequencyHz;//we use floating point math for maximum precision.<br />
word frequencycHz;//this we will convert to a string to display.<br />
byte thisDigit;<br />
byte precision; //to keep track of roughly how precise the frequency will be.<br />
byte updateDisplay; //True this byte when display should be updated.<br />
byte trackDigits; //track whether display of significant digits has started.<br />
word delayCounter;//can count 0 to 65535<br />
<br />
void recordTime(){//this is the interrupt service routine to be called whenever switchPin's voltage falls<br />
//we want as few instructions and variables inside this routine as possible, so we just gather the time and go.<br />
hitTime = micros();//reads a clock in microseconds<br />
}<br />
void sendToDisplay(byte byteToSend, word microsecondsToDelay ){<br />
for (int shiftCounter = 0; shiftCounter < 8; shiftCounter++) {//repeat 8 times<br />
if (byteToSend > 127) {//get most significant bit. If a byte is over 127, then it must start with a one.<br />
digitalWrite(DATAP,HIGH);//Represent one, or on<br />
} else {<br />
digitalWrite(DATAP,LOW);//Represent zero, or off<br />
}<br />
//data setup time, the minimum time the display chip needs the data pin to be stable before the clock rises, is 10ns<br />
NOP; // Do nothing for one cycle, delaying 62.5ns on a 16MHz AtMega<br />
digitalWrite(CLKP,HIGH);//raise voltage on clock pin, forcing display chip to read from the data pin.<br />
//data hold time, the maximum time the display chip might need to read the data after the clock rises, is 20ns<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
digitalWrite(CLKP,LOW);//get ready for next time<br />
for (int clockDelay=16;clockDelay>0;clockDelay--){//repeat 16 times<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
}<br />
byteToSend *=2;//line up second-most significant bit as the most significant bit<br />
}<br />
// shiftOut(DATAP, CLKP, MSBFIRST, byteToSend);//this built-in library was working extremely slowly (~1Hz).<br />
while(microsecondsToDelay-- > 0){//If microsecondsToDelay is above zero, do the following. Also decrement microsecondsToDelay by one.<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
NOP; //By using 15 of these, we delay a predictable microsecond. A loop would be neater but probably less precise<br />
NOP; //But to be accurate we'd need to know how long it takes to execute the while statement each time.<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
NOP;<br />
}<br />
}<br />
void waitingMode(){<br />
precision = 0;//We use 'precision' as a flag to mark whether we've already made one clock-reading.<br />
digitalWrite(RSPin, LOW); //about to issue a command<br />
for (int clockDelay=160;clockDelay>0;clockDelay--){//wait ten microseconds<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
}<br />
sendToDisplay(B00000001, 0); //clear display and go home<br />
for (int clockDelay=1600;clockDelay>0;clockDelay--){//wait a hundred microseconds<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
}<br />
digitalWrite(RSPin, HIGH); //about to issue data<br />
//Address setup time is supposed to be 10 µs, but the following W goes missing without the following 100 microsecond delay.<br />
for (int clockDelay=1600;clockDelay>0;clockDelay--){<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
}<br />
sendToDisplay('W',0);//The compiler interprets 'W' as the ascii value for W, B01010111.<br />
sendToDisplay('a',0);<br />
sendToDisplay('i',0);<br />
sendToDisplay('t',0);<br />
sendToDisplay('i',0);<br />
sendToDisplay('n',0);<br />
sendToDisplay('g',0);<br />
#ifdef serialDebug//Only do the following if serialDebug is defined above.<br />
Serial.println("Waiting");<br />
#endif<br />
}<br />
<br />
void setup(){//This is the routine done only once when the board is reset, or first receives power.<br />
pinMode(DATAP, OUTPUT);//Prepare these pins to assert a voltage<br />
pinMode(CLKP, OUTPUT);<br />
pinMode(chipSelectPin, OUTPUT);<br />
pinMode(RSPin, OUTPUT);<br />
pinMode(switchPin, INPUT_PULLUP);//get switchPin ready for interrupt input, activating (internal) pullup resistor Hall Switch expects.<br />
<br />
#ifdef serialDebug//Only do the following if serialDebug is defined above.<br />
// initialize serial communication at 9600 bits per second:<br />
Serial.begin(9600);//prepare the serial lines to use 9600 baud<br />
#endif<br />
<br />
delay(500); //Wait half a second for display to power.<br />
digitalWrite(chipSelectPin, HIGH);//Set high to make sure CSB has fallen once before beginning commands,<br />
for (int clockDelay=1600;clockDelay>0;clockDelay--){<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
} <br />
digitalWrite(CLKP, LOW);//DOGM081 expects to take in data on rising clock.<br />
digitalWrite(chipSelectPin, LOW);//and keep it low, unless you need the other pins to do something not with this display<br />
digitalWrite(RSPin, LOW);//About to issue commands<br />
//The following lines from Electronic Assembly's data sheet, with cursor mode altered.<br />
//One might repeat the following command many times to get the attention of the ST7032 driver<br />
sendToDisplay(B00110000, 27); //Function Set, 1 line, instruction table 0<br />
sendToDisplay(B00110001, 27); //Function Set, 1 line, instruction table 1<br />
sendToDisplay(B00110001, 27); //Function Set, 1 line, instruction table 1, repeat request recommended by ST7032 datasheet<br />
sendToDisplay(B00011100, 27); //Bias 1/4, 1 line LCD<br />
sendToDisplay(B01010001, 27); //Booster off, set contrast C5, C4<br />
sendToDisplay(B01101010, 0); //set voltage follower on, gain 2/7<br />
delay(200);//recommended by ST7032 datasheet<br />
sendToDisplay(B01110100, 27); //set contrast C3, C2, C1<br />
sendToDisplay(B00001100, 27); //display on, cursor off, blink cursor position<br />
sendToDisplay(B00000001, 3); //clear display, home cursor<br />
delay(2);//takes over a millisecond to clear<br />
sendToDisplay(B00000110, 2); //entry from right to left, no scrolling.<br />
waitingMode();<br />
attachInterrupt(switchPinInt, recordTime, FALLING);//Go to recordTime whenever interrupt switchPinInt goes from high to low<br />
}//Done setting up<br />
<br />
void loop() {<br />
if(hitTime>0){//There's a 1 in 4 billion chance hitTime is zero but represents an actual reading.<br />
if (0==precision) {//this is first time switch, so no reference, so only set precision flag and record time.<br />
precision = 1;//so we don't end up here next time<br />
lastTime = hitTime;//record now for reference next time.<br />
}<br />
else<br />
{ periodMicroSeconds = hitTime - lastTime; //if timer has overflowed once since last time, ok since this subtraction will overflow back.<br />
if (periodMicroSeconds > debounceMicroSeconds) {//debounce by rejecting too-fast times<br />
precision = 1;//Set our flag so we know we have a valid period<br />
updateDisplay = 1;//We have a new period, so let's update the display<br />
lastTime = hitTime;//record now for reference next time.<br />
}<br />
}<br />
hitTime=0;//skip the above routine until another hit.<br />
}<br />
//We don't want to read micros every trip through this loop--Reading micros causes interrupt issues.<br />
// So, if we increase a 16-bit (word) variable by one every trip, it will only overflow back to zero once every 65536 (2^16) trips.<br />
if(0 == delayCounter++){<br />
microsSince = micros() - lastTime;//Keep track of idleness.<br />
}<br />
if(precision > 0 && microsSince > maxTime){//It's been more than maxTime since the last hit, so go back into waiting mode<br />
waitingMode();<br />
}<br />
if (updateDisplay) {<br />
updateDisplay = 0;//Don't come back here until next update is requested by changing this flag back to 1<br />
frequencyHz = 1000000.0 / (float)periodMicroSeconds;//float math for best precision over a wide range.<br />
if (periodMicroSeconds>20) {precision = 2;}//Only when a rounded divisor is about 20 does the quotient have any meaning past the first digit.<br />
if (periodMicroSeconds>200) {precision = 3;}<br />
if (periodMicroSeconds>2000) {precision = 4;}<br />
if (periodMicroSeconds>20000) {precision = 5;}<br />
#ifdef serialDebug<br />
Serial.println(periodMicroSeconds);// print the value you read:<br />
#endif<br />
digitalWrite(RSPin, LOW); //about to issue a command<br />
sendToDisplay(B00000001, 0); //clear display and go home<br />
//wait a long time for display to clear.<br />
for (int clockDelay=3200;clockDelay>0;clockDelay--){<br />
NOP; // delay 62.5ns on a 16MHz AtMega<br />
}<br />
digitalWrite(RSPin, HIGH); //about to issue data<br />
frequencyHz *= 100.0; //Temporarily, if you uncomment the re-conversion below, convert to cHz.<br />
frequencycHz = (word)frequencyHz;//round to a number between 0 and 65535<br />
//frequencyHz /= 100.0; //Convert back to Hz in case we ever need it.<br />
trackDigits = 0;//We're going to display the correct precision by tracking significant digits<br />
//The highest a word integer can be is 65535, so the most significant digit we could possibly have is the ten thousands digit.<br />
thisDigit = frequencycHz / 10000;//get ten-thousands digit. This is integer math, so no fractions in result.<br />
if(thisDigit > 0){<br />
trackDigits = 1; //start tracking significant digits<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);<br />
}<br />
frequencycHz = frequencycHz % 10000;//discard ten-thousands by modulo arithmetic<br />
thisDigit = frequencycHz / 1000;//get thousands digit<br />
if(trackDigits){<br />
if(++trackDigits > precision) {//Increment trackDigits, then check to see whether it has exceeded precision.<br />
sendToDisplay(48, 0);//We've run beyond our precision, so display a placeholding zero.<br />
} else {<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//show this digit since it is significant<br />
}<br />
} else {<br />
if(thisDigit > 0){<br />
trackDigits = 1; //start tracking significant digits<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);<br />
}<br />
}<br />
frequencycHz = frequencycHz % 1000;//discard thousands<br />
thisDigit = frequencycHz / 100;//get hundreds digit<br />
if(trackDigits){<br />
if(++trackDigits > precision) {//Increment trackDigits, then check to see whether it has exceeded precision.<br />
sendToDisplay(48, 0);//We've run beyond our precision, so display a placeholding zero.<br />
} else {<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//show this digit since it is significant<br />
}<br />
} else {<br />
if(thisDigit > 0){<br />
trackDigits = 1; //start tracking significant digits<br />
}<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//this time, even if zero, we display, since placeholder needed for decimal point.<br />
}<br />
sendToDisplay('.', 0);//send a decimal point<br />
frequencycHz = frequencycHz % 100;//discard hundreds<br />
thisDigit = frequencycHz / 10;//get tens digit<br />
if(trackDigits){<br />
if(++trackDigits <= precision) {//increment trackDigits, then check to see whether it hasn't exceeded precision<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//show this digit since it is significant<br />
}<br />
} else {<br />
if(thisDigit > 0){<br />
trackDigits = 1; //start tracking significant digits<br />
}<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//show this digit since it is significant<br />
}<br />
thisDigit = frequencycHz % 10;//get ones digit<br />
if(++trackDigits <= precision) {//increment trackDigits, then check to see whether it hasn't exceeded precision<br />
thisDigit +=48; //convert to ASCII<br />
sendToDisplay(thisDigit, 0);//show this digit since it is significant<br />
}<br />
sendToDisplay('H', 0);//always show units if they exist<br />
sendToDisplay('z', 0);<br />
}<br />
}<br />
<br />
[[Category:Electronics]]</div>Shawn