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