Measuring PWM ratios
JSWrightOC
Posts: 49
I'm trying to write a routine which will determine the ratio of an incoming PWM signal, ideally expressed as a value 0-255 to indicate 0% - 100%. I know that 0% and 100% are going to be DC values, in which case PULSIN will return 0. I will account for this using other methods not described here.
What I have been doing thus far is to measure the high period using PULSIN, then measuring the low period using PULSIN, and adding the two together to get the total time period. The PWM frequency I am trying to measure is about 100Hz, so the total period time is approx. 10mS - well within the measurement range of all Stamp versions.
Here is where I begin having trouble. Because the Stamp is limited to integer values, I cannot simply perform tHIGH / tTOTAL and then multiply the result by 255, otherwise that would be the obvious solution. What I have tried thus far almost works, but again due to the integer math I am running into trouble. If I divide tTOTAL by 256, and then divide tHIGH by the result, I get a value that at first appears to be 0-255, but actually will exceed 255 due to rounding errors in the first equation. Incidentally this only works when tTOTAL is greater than 256. I know that the Divide Modulus (//) operator might be useful here, though all it does is return the remainder that would be left behind after integer division, and nothing creative is coming to mind.
Here's an example.
=============================
LoTime VAR Word
HiTime VAR Word
ToTime VAR Word
PWMVal VAR Word
Divisor VAR Byte
PULSIN 0, 0, LoTime
PULSIN 0, 1, HiTime
ToTime = HiTime + LoTime
Divisor = ToTime / 256
PWMVal = HiTime / Divisor
DEBUG DEC5 ?PWMVal
=============================
In this code example PWMVal (which should be 0-255) is a Word value because it isn't 0-255; otherwise it could be a Byte value. Divisor will always be less than 256, because the input period will never come close to 65,535 PULSIN ticks long in this application.
Some real-world numbers:
HiTime = 04062
LoTime = 01112
04062 + 01112 = 05174
05174 / 256 = 20.2109375 (NOTE that the Stamp ignores everything to the right of the decimal point; this is the cause of my math errors)
04062 / 20 = 203.1 (the .1 gets truncated, which in this case is not a problem because I just need 8 bits of resolution)
If we did not ignore the fractional part as shown above:
04062 / 20.2109375 = ~200.98 (I truncated the remaining fractional part for the sake of legibility)
As you can see, 203.1 is not equal to ~200.98. The extent of this error depends on the exact value of ToTime. As ToTime reaches something evenly divisible by 256, the error is minimized. When the remainder of ToTime / 256 is the greatest, the error will also be the greatest.
Unfortuniately I have to account for long-term variations and component tolerances which will affect the frequency of the PWM signal, so I cannot simply assume that tTOTAL will always be a certain value, and use constants that will give me the numbers I need. Tthe numbers shown above were derived from a BS2 with a measured input frequency of 97.09Hz using a frequency counter.
I might be missing something obvious, in which case I deserve a firm smack to the forehead, because it's got me stumped! Help would be much appreciated.
What I have been doing thus far is to measure the high period using PULSIN, then measuring the low period using PULSIN, and adding the two together to get the total time period. The PWM frequency I am trying to measure is about 100Hz, so the total period time is approx. 10mS - well within the measurement range of all Stamp versions.
Here is where I begin having trouble. Because the Stamp is limited to integer values, I cannot simply perform tHIGH / tTOTAL and then multiply the result by 255, otherwise that would be the obvious solution. What I have tried thus far almost works, but again due to the integer math I am running into trouble. If I divide tTOTAL by 256, and then divide tHIGH by the result, I get a value that at first appears to be 0-255, but actually will exceed 255 due to rounding errors in the first equation. Incidentally this only works when tTOTAL is greater than 256. I know that the Divide Modulus (//) operator might be useful here, though all it does is return the remainder that would be left behind after integer division, and nothing creative is coming to mind.
Here's an example.
=============================
LoTime VAR Word
HiTime VAR Word
ToTime VAR Word
PWMVal VAR Word
Divisor VAR Byte
PULSIN 0, 0, LoTime
PULSIN 0, 1, HiTime
ToTime = HiTime + LoTime
Divisor = ToTime / 256
PWMVal = HiTime / Divisor
DEBUG DEC5 ?PWMVal
=============================
In this code example PWMVal (which should be 0-255) is a Word value because it isn't 0-255; otherwise it could be a Byte value. Divisor will always be less than 256, because the input period will never come close to 65,535 PULSIN ticks long in this application.
Some real-world numbers:
HiTime = 04062
LoTime = 01112
04062 + 01112 = 05174
05174 / 256 = 20.2109375 (NOTE that the Stamp ignores everything to the right of the decimal point; this is the cause of my math errors)
04062 / 20 = 203.1 (the .1 gets truncated, which in this case is not a problem because I just need 8 bits of resolution)
If we did not ignore the fractional part as shown above:
04062 / 20.2109375 = ~200.98 (I truncated the remaining fractional part for the sake of legibility)
As you can see, 203.1 is not equal to ~200.98. The extent of this error depends on the exact value of ToTime. As ToTime reaches something evenly divisible by 256, the error is minimized. When the remainder of ToTime / 256 is the greatest, the error will also be the greatest.
Unfortuniately I have to account for long-term variations and component tolerances which will affect the frequency of the PWM signal, so I cannot simply assume that tTOTAL will always be a certain value, and use constants that will give me the numbers I need. Tthe numbers shown above were derived from a BS2 with a measured input frequency of 97.09Hz using a frequency counter.
I might be missing something obvious, in which case I deserve a firm smack to the forehead, because it's got me stumped! Help would be much appreciated.
Comments
40620+11120 = 51740
51740 / 256 = 202· (202.109375)
40620 / 202 = 201 (201.089108...)
How much resolution do you need though is the real question.
The initial pulsin has a 1/2%·error as well
97.09 HZ should return a total of 5149.86095... using 2us pulsin. assuming the 97.09 from the frequency counter is correct. You still will have a 2us * 4 = 8us potential error as the two start times and two stop times may be rounded from the initial input. About 0.012% error between highest and lowest readings.
There is also a 0.012% error rate using 2us as a counter as you start and stop 4 times. If the pulse came in on the boundry of all four start/stop times.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Think Inside the box first and if that doesn't work..
Re-arrange what's inside the box then...
Think outside the BOX!
Post Edited (metron9) : 1/24/2009 4:19:44 PM GMT
HiTime = 04062
LoTime = 01112
N = HiTime
D = HiTime + LoTime
'
binary division loop
for J=15 to 0 ' 16 bits
N=N//D<<1 ' remainder*2
F.bit0(J)=N/D ' next bit
next
'
' F = 51450
DEBUG DEC F, "=", DEC F/256, " * 256 + ", DEC F//256, CR ' prints 51450 = 200*256+250
DEBUG "rounded = ", DEC F/256 + (F//256/128), CR ' prints rounded=201
F = F ** 10000 ' convert to 0.00 to 99.99 %
DEBUG DEC F/100, ".", DEC2 F, "%", CR ' prints 78.50%
For explanation see www.emesys.com/BS2math2.htm#binary_long
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Tracy Allen
www.emesystems.com
Really what I am hoping to do is to have a value that I can put through a lookdown table, so that I can translate a number of arbitrary PWM levels to specific values. It would be most logical to translate the PWM input to a value that varies from 0-255, but really any range will do for this application. Precision is not dramatically important, as the end result will be a number that is 0-7, but the end result does not scale linearly with the PWM ratio. What I do need though was something that would work with a given range of input frequencies without having bugs as a result of math errors.
metron9,
I know there is going to be a 1-2 count "bobble" in what the Stamp sees for the high and low times, as the input pulses are not synchronized with the timebase (this phenomenon is also present in many frequency counters). While my meter read 97.09Hz, I did the math on the values the Stamp was indicating and the two did not match - I attributed this mainly to operating frequency tolerance of the ceramic resonator (which the Stamp uses for its timebase), and possibly the calibration of my meter. What is the 1/2% error using PULSIN that you speak of?
Tracy,
I'm not suprised that you had a method of performing long division, considering the nature of your work. I read the article you linked me to, and that's pretty neat. I think it's too bad Parallax did not (or could not?) impliment something like this in machine language as a PBASIC instruction.
So I can better understand what the algorithm does, I is the integer part of the result, and F is the fractional part, expressed in units of 65536... where ~0.0000152587890625 (1/65536) would be F=1, ~0.000030517578125 (2/65536) would be F=2, etc. correct?
However, the initial accuracy using the truncated numbers won't matter anyway since you are going to scale it down to a number 0-7.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Think Inside the box first and if that doesn't work..
Re-arrange what's inside the box then...
Think outside the BOX!
I should have mentioned that the starting value of D (the divisor) has to be less than 32768, so that the shift left operation will work without overflow. That did not seem to be an issue with your example values.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Tracy Allen
www.emesystems.com
metron9,
The truncated numbers were not a significant issue with regard to the accuracy of the output, however I don't think making the rounding errors 10 times smaller will help quite enough.
51730+00010 = 51740
51740 / 255 = 202 (202.901960)
51730 / 202 = 256 (256.089108)
If I used a byte value for the output it would overflow. I also had an error in my original example; I should be dividing by 255 since I wanted 255 = 100%. Even still, it could have the potential to result in an overflow, and the error is still frequency dependent. This would definitely work if I were starting with smaller numbers, however.
Tracy,
I don't think the limitations of D will be a concern, as the lowest frequency I am planning on working with would be 60Hz (0.0167S) which would be a total time period of 8333 counts on a BS2 and 20833 on a BS2sx. Before I PULSIN and do the binary long division I'll be checking the input to see if it is toggling slower than 50Hz (COUNT <pin>, 20, <variable> if it were a BS2, 50 for an SX), and if so I check the pin to see if it's 0 or 1. I then bypass the long division algorithm, simply giving a 000 or 255 for the result; this should trap any bad input before it gets to the division. I know that a slowly changing signal will result in the output toggling between 000 and 255, but this is an acceptable failure mode for my application.
Once again, thank you both for the info, and for an excellent group of people on this forum!