Hello,
I am using an Arduino UNO R3 with the 1.6.13 IDE
My sketch is attempting to measure the AC mains frequency and drive a voltmeter. Here is the code:
/*void loop() __attribute__((optimize("-O0"))); // Compiler directive - do not optimise for loops */
const int pulse_pin = 2; // UNO Sampling pin
const int meter_pin = 11; // Do not use 5 or 6 as these interact with millis()
int pulse_cnt = 0;
int sample_size = 120; // Sample size, should be at least 2 times expected frequency
// Don't make lower otherwise the processor "freezes"!
long unsigned start_time, end_time;
int meter_value = 0;
int prev_meter_value = 0;
int value_change = 0;
int delay_factor = 0;
int old_analog = -999; // Needle value last displayed
float elapsed = 0;
float frequency = 0;
void setup()
{
Serial.begin(9600);
pinMode(pulse_pin, INPUT);
pinMode(meter_pin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(pulse_pin), interrupt_count, RISING); // code for counting the increasing values
Serial.println("Starting...");
start_time = millis(); // Sampling start time
end_time = 0; // Sampling end time
}
void loop()
{
if (end_time != 0) {
noInterrupts(); // Disable interrupts to ensure accuracy of readings
elapsed = end_time - start_time;
frequency = (sample_size / elapsed) * 1000;
frequency = frequency - 0.03; // Adjust frequency to be in line with web site
if (frequency >= 49.50 and frequency <= 50.50) { // Only display if valid data
// meter_value = mapFloat(frequency,49.50,50.50,0,255);
// meter_value = map(int_freq,4950,5050,0,255);
meter_value = (frequency - 49.5) * 255; // Avoid map function and do it yourself
Serial.print("Frequency: ");
Serial.print(frequency);
Serial.print(" Elapsed: ");
Serial.print(elapsed);
Serial.print(" Meter value=");
Serial.println(meter_value);
// analogWrite(meter_pin, meter_value);
//move_needle(meter_value,10);
}
//Reset variables
pulse_cnt = 0;
start_time = millis(); // Was start_time=millis();
end_time = 0;
interrupts(); //Enable interrupts again
}
}
void interrupt_count()
{
pulse_cnt++;
if (pulse_cnt == sample_size) { //signal when set number of samples have been taken
end_time = millis();
}
}
void move_needle(int meter_value, byte ms_delay) {
// Move the needle util new value reached
while (!(meter_value == old_analog)) {
if (old_analog < meter_value) old_analog++;
else old_analog--;
if (ms_delay == 0) old_analog = meter_value; // Update immediately id delay is 0
analogWrite(meter_pin, old_analog);
// Slow needle down slightly as it approaches new position
if (abs(old_analog - meter_value) < 10) ms_delay += ms_delay / 5;
// Wait before next update
delay(ms_delay);
}
}
If I execute the code with the analogWrite instruction commented out it works perfectly (n.b. I have a reputable source to compare my results with) as shown by the Serial monitor output here:
Starting...
Frequency: 50.37 Elapsed: 2381.00 Meter value=221
Frequency: 49.95 Elapsed: 2401.00 Meter value=114
Frequency: 49.95 Elapsed: 2401.00 Meter value=114
Frequency: 49.97 Elapsed: 2400.00 Meter value=119
Frequency: 49.97 Elapsed: 2400.00 Meter value=119
Frequency: 49.93 Elapsed: 2402.00 Meter value=109
Frequency: 49.95 Elapsed: 2401.00 Meter value=114
Frequency: 49.93 Elapsed: 2402.00 Meter value=109
Frequency: 49.93 Elapsed: 2402.00 Meter value=109
Frequency: 49.95 Elapsed: 2401.00 Meter value=114
If I then uncomment the analogWrite instruction, things start to go awry:
Starting...
Frequency: 50.10 Elapsed: 2394.00 Meter value=151
Frequency: 50.43 Elapsed: 2378.00 Meter value=237 <<< Wrong value
Frequency: 50.41 Elapsed: 2379.00 Meter value=232
Frequency: 49.99 Elapsed: 2399.00 Meter value=125
Frequency: 50.39 Elapsed: 2380.00 Meter value=226 <<< Wrong value
Frequency: 49.99 Elapsed: 2399.00 Meter value=125
Frequency: 49.97 Elapsed: 2400.00 Meter value=119
Frequency: 49.99 Elapsed: 2399.00 Meter value=125
Frequency: 50.39 Elapsed: 2380.00 Meter value=226 <<< Wrong value
Frequency: 49.99 Elapsed: 2399.00 Meter value=125
Frequency: 50.41 Elapsed: 2379.00 Meter value=232 <<< Wrong value
Things I have tried so far:
Used every PWM pin in turn,
Replaced the UNO with a Nano,
Tried the map function then tried the mapFloat function before resorting to creating my own formula to evaluate meter_value. (I read somewhere that the map function sometimes caused problems with timing),
Inserted a compiler directive which I thought might help.
I'd tighten up the code between the noInterrupts() and interrupts() to just deal with the variables potentially modified by the interrupts. The floating point math and a stream of serial text at 9600 baud could take a while.
Maybe:
noInterrupts(); // Disable interrupts to ensure accuracy of readings
elapsed = end_time - start_time;
//Reset variables
pulse_cnt = 0;
end_time = 0;
interrupts(); //Enable interrupts again
start_time = millis(); // Was start_time=millis();
frequency = (sample_size / elapsed) * 1000;
...
Hi DaveX,
I implemented your suggested code but it didn't improve matters.
I also tried changing the Serial baud rate from 9600 to 115200 and also
commented out completely the Serial.print statements as I can see the resulting meter value on the meter. Both changes did not work either which seems to confirm my suspicion of the analogWrite statement itself.
Hi DaveX
It's just a cheap Chinese panel meter. I've got a digital multi-meter and an old fashioned moving coil multi-meter - I'll try those and reply soon.
In reply to "What's wrong with the values?" they are completely out of range from the expected values - by that I mean I have a comparable system ( it uses LEDs instead of a meter and the circuitry is identical for both projects) so I can see what the correct figure should be by the appropriate LED illuminating.
I take your point regarding rounding but I'm not overly fussed about an error of +/- 1 in the meter value. As I said earlier I'm about to test with two alternate meters. As a slight aside to this topic I also have a project which uses three 5V panel meters with modified scales to display the time in hours , minutes and seconds which again uses analogWrite without issue. However these meters are a different model to the one I'm using for the frequency project. Back soon with the meter results.
OK. I tried both my digital multi-meter and also my ALTAI moving coil multi-meter - still getting erroneous results unfortunately. So I guess we can rule out the meter.
I also tried to remove the floating point maths like this:
if (end_time != 0) {
noInterrupts(); // Disable interrupts to ensure accuracy of readings
elapsed = end_time - start_time;
pulse_cnt = 0;
end_time = 0;
start_time = millis();
interrupts(); //Enable interrupts again
//frequency = (sample_size / elapsed) * 1000;
int_freq=((sample_size/elapsed)*1000)*100; // int_freq declared as int
//frequency = frequency - 0.03; // Adjust frequency to be in line with web site
int_freq = int_freq - 3;
if (int_freq >= 4950 and int_freq <= 5050) { // Only display if valid data
// if (frequency >= 49.50 and frequency <= 50.50) { // Only display if valid data
// meter_value = mapFloat(frequency,49.50,50.50,0,255);
// meter_value = map(int_freq,4950,5050,0,255);
//meter_value = (frequency - 49.5) * 255; // Avoid map function and do it yourself
meter_value = ((int_freq - 4950) * 255) / 100; // Avoid map function and do it yourself
Serial.print("Frequency: ");
Serial.print(int_freq);
Serial.print(" Elapsed: ");
Serial.print(elapsed);
Serial.print(" Meter value=");
Serial.println(meter_value);
analogWrite(meter_pin, meter_value);
//move_needle(meter_value,10);
}
On the int frequency, I'd try the multiplies before the divides, so, for example, 120/2379 doesn't truncate to zero.
Without the hardware setup, or a clear error, it's hard to tell what the problem is. From our view of your project, the numbers seem reasonable, but it is hard to tell a defect from not a defect. For instance, why is the 232 line not a wrong value?
How does what was delivered differ from what was expected?
Ok I'll try and re-arrange the coding for int_frequency however this exact code is used for the LED version of the sketch which works fine (zero truncation notwthstanding!).
Regarding your point "For instance, why is the 232 line not a wrong value?"
that's an error on my part, I should have denoted that line as in error.
In terms of hardware setup do you mean a circuit diagram or photograph of the components - I'm not sure what you mean, please elaborate for me.
Hi DaveX,
I forgot to answer your last question - "How does what was delivered differ from what was expected?"
As I said in post 11, the wrong values deviate from the expected which I can monitor via my LED based application. If the values jumped from say 49.99 to 50.39 in the real world that would represent a sudden (i.e. in the region of a couple of seconds) increase in the power output to the grid. I think that's highly unlikely especially when the number of occurrences are as high as the data I've presented to you demonstrates.
So the issue isn't in the calculations, but that either there are spurious extra counts, or that the counts are correct but the timing is wrong.
In this part of the world, the grid power phase is well controlled so different systems can be linked together efficiently.
120 cycles at 50Hz should be 2.4sec, or 2400 msec. The 2.4 sec reporting code shouldn't have a big effect. The 50Hz interrupt should have 1/50s = 20ms = 20000us = 320000 clock cycles to work with.
If it's not the circuitry, the problem may be the timing. Have you considered using micros() instead of millis()?
Try initializing the timer inside the interrupt routine. Remove or comment out line 63:
start_time = millis(); // Was start_time=millis();
and change the ISR to
void interrupt_count()
{
if (!pulse_cnt) { // first RISING? start timing
start_time = millis();
}
pulse_cnt++;
if (pulse_cnt == sample_size) { //signal when set number of samples have been taken
end_time = millis();
}
and declare all variables involved as volatile:
volatile int pulse_cnt = 0;
volatile int sample_size = 120;
volatile long unsigned start_time, end_time;