PASM critique requested
turbosupra
Posts: 1,088
Hello,
This code compiles, but I don't know if it does what is intended (probably not actually) because I just started learning and haven't learned how to debug very effectively yet with the pst and PASM. I'm using MUL, which I accidentally discovered even though it isn't listed in the PASM manual, so I'm not even sure it does what I think it does. I'm sure there are tricks to make this code faster and shorter, since I'm new I would guess that I write very literally and in what could be termed the "long way", but while learning I need to see it written out. There are probably extra variables, because parts of the code are copied from another PASM code block (thank you anonymous code donator , you know who you are ).
What I'm trying to do
1.) Measure frequency rising edge to following rising edge (a frequency rate of anywhere from 180hz to 9000hz)
2.) Measure frequency falling edge to following falling edge
3.) Wait until I have 8 measurement
4.) After accumulating 8 measurements, divide by 8 to get an average
5.) Count frequency logic rising edges
6.) Multiply average frequency rate by 110 and 90 (for 110% and 90%) and then compare them to the current frequency measurement multiplied by 100
7.) Identify "gaps" longer than the average and count them at a per second rate
8.) Compare shortest amount of cycles to shortest on record, replace if necessary
9.) Compare longest amount of cycles to longest on record, replace if necessary
I believe I can do the multiply portion as shown below, in case MUL does not do what I think it does.
If you get this far, I appreciate you reading through what is probably virtual spaghetti.
This code compiles, but I don't know if it does what is intended (probably not actually) because I just started learning and haven't learned how to debug very effectively yet with the pst and PASM. I'm using MUL, which I accidentally discovered even though it isn't listed in the PASM manual, so I'm not even sure it does what I think it does. I'm sure there are tricks to make this code faster and shorter, since I'm new I would guess that I write very literally and in what could be termed the "long way", but while learning I need to see it written out. There are probably extra variables, because parts of the code are copied from another PASM code block (thank you anonymous code donator , you know who you are ).
What I'm trying to do
1.) Measure frequency rising edge to following rising edge (a frequency rate of anywhere from 180hz to 9000hz)
2.) Measure frequency falling edge to following falling edge
3.) Wait until I have 8 measurement
4.) After accumulating 8 measurements, divide by 8 to get an average
5.) Count frequency logic rising edges
6.) Multiply average frequency rate by 110 and 90 (for 110% and 90%) and then compare them to the current frequency measurement multiplied by 100
7.) Identify "gaps" longer than the average and count them at a per second rate
8.) Compare shortest amount of cycles to shortest on record, replace if necessary
9.) Compare longest amount of cycles to longest on record, replace if necessary
DAT org 0 frcntr mov tmp1, par ' start of structure rdlong tmp2, tmp1 ' get pin#, store it in tmp2 mov ctra, POS_DETECT ' ctra measures high phase add ctra, tmp2 mov frqa, #1 mov ctrb, NEG_DETECT ' ctrb measures low phase add ctrb, tmp2 mov frqb, #1 mov mask, #1 ' create pin mask shl mask, tmp2 ' save addresses of hub storage add cycleptr, tmp1 add toothcntptr, tmp1 add averageptr, tmp1 add maxptr, tmp1 add minptr, tmp1 mov timeStamp, cnt restart waitpne mask, mask ' wait for 0 phase mov lowCycle, phsa mov phsa, #0 ' clear high phase counter mov lowCntLog, cnt ' wait for pin == 1 highphase waitpeq mask, mask add highCycle, phsb mov phsb, #0 ' clear low phase counter mov highCntLog, cnt ' write number of cycles measured to fcCycles 'wrlong lowCycle, cycleptr mov prevCycle9, prevCycle8 mov prevCycle8, prevCycle7 mov prevCycle7, prevCycle6 mov prevCycle6, prevCycle5 mov prevCycle5, prevCycle4 mov prevCycle4, prevCycle3 mov prevCycle3, prevCycle2 mov prevCycle2, lowCycle cmp prevCycle9, #0 WR, WZ ' Make sure I have 8 previous values for an average if_be jmp #restart add avgCycle, prevCycle9 ' sum prevCycle9 into avgCycle add avgCycle, prevCycle8 ' sum prevCycle8 into avgCycle add avgCycle, prevCycle7 ' sum prevCycle7 into avgCycle add avgCycle, prevCycle6 ' sum prevCycle6 into avgCycle add avgCycle, prevCycle5 ' sum prevCycle5 into avgCycle add avgCycle, prevCycle4 ' sum prevCycle4 into avgCycle add avgCycle, prevCycle3 ' sum prevCycle3 into avgCycle add avgCycle, prevCycle2 ' sum prevCycle2 into avgCycle shl avgCycle, 3 ' divide avg by 8 to get the actual average add teethPerRev, #1 ' increment this for each frequency logic high I get mov lowCycleTmp, lowCycle mul lowCycleTmp, #100 ' multiple lowCycle by 100% for resolution and no floats mov avgCycleTmpH, avgCycle mul avgCycleTmpH, #110 ' multiple avgCycle by 110% mov avgCycleTmpL, avgCycle mul avgCycleTmpL, #90 ' multiple avgCycle by 90% cmp avgCycleTmpH, lowCycleTmp WR, WZ if_a mov lowCycle, avgCycle ' if it's above 110%, toss it and replace it with the average if_a mov gapCntTimeStampH, highCntLog ' log a timestamp of when the frequency is greater than 110% of the average if_a mov gapsPerSec, #1 ' reset back to 1 if_a mov teethPerRev, #1 ' reset back to 1 mov localCnt, cnt ' create copy of cnt sub localCnt, timeStamp ' subtract timeStamp from copy of cnt cmp localCnt, #1 ' is copy of cnt is greater than timeStamp? if_a mov timeStamp, cnt ' if so, reset timeStamp if_a mov gapsPerSecPeak, gapsPerSec ' copy gapsPerSec to a Peak value holder if_a mov gapsPerSec, #0 ' reset gapsPerSec cmp avgCycleTmpL, lowCycleTmp WR, WZ if_b mov lowCycle, avgCycle ' if it's below 90%, toss it and replace it with the average if_b mov gapCntTimeStampL, lowCntLog ' log a timestamp of when the frequency is less than 90% of the average cmp minc, lowCycle WR, WZ ' compare lowCycle to minimum on record if_b mov minc, lowCycle ' overwrite minimum if lowCyle is indeed shorter cmp maxc, lowCycle WR, WZ ' compare lowCycle to maximum on record if_a mov maxc, lowCycle ' overwrite maximum if lowCyle is indeed longer wrlong lowCycle, cycleptr wrlong teethPerRev, toothcntptr wrlong averagex, averageptr wrlong maxc, maxptr wrlong minc, minptr jmp #restart nopinstr mov 0, 0 NR ' -------------------------------------------------------------------------------------------------- POS_DETECT long %01000 << 26 NEG_DETECT long %01100 << 26 cycleptr long 4 toothcntptr long 8 averageptr long 12 maxptr long 16 minptr long 20 ' Standard variable initialization here maxc long 444444 ' cycles at 300rpm minc long 444444 ' cycles at 300rpm tooth long 0 database long 0 averagex avgcnt long 8 dontcount long 8000'13400 ' a floor for the minimum amount of time to pass by avgCycle long 0 prevCycle9 long 0 prevCycle8 long 0 prevCycle7 long 0 prevCycle6 long 0 prevCycle5 long 0 prevCycle4 long 0 prevCycle3 long 0 prevCycle2 long 0 lowCycleTmp long 0 avgCycleTmpH long 0 avgCycleTmpL long 0 lowCntLog long 0 highCntLog long 0 gapCntTimeStampH long 0 gapCntTimeStampL long 0 gapsPerSec long 0 gapsPerSecPeak long 0 localCnt long 0 timeStamp long 0 teethPerRev long 0 tmp1 res 1 tmp2 res 1 mask res 1 ' mask for frequency input pin lowCycle res 1 ' low cycles in input period highCycle res 1 ' high cycles in input period fit 492
I believe I can do the multiply portion as shown below, in case MUL does not do what I think it does.
mov lowCycleTmp, lowCycle mul lowCycleTmp, #100 ' multiple lowCycle by 100% for resolution and no floats {this could be shl lowCycleTmp, #3 add lowCycleTmp, lowCycle add lowCycleTmp, lowCycle} mov avgCycleTmpH, avgCycle mul avgCycleTmpH, #110 ' multiple avgCycle by 110% {this could be shl avgCycleTmpH, #3 add avgCycleTmpH, avgCycle add avgCycleTmpH, avgCycle add avgCycleTmpH, avgCycle} mov avgCycleTmpL, avgCycle mul avgCycleTmpL, #90 ' multiple avgCycle by 90% {this could be shl avgCycleTmpL, #3 add avgCycleTmpL, avgCycle
If you get this far, I appreciate you reading through what is probably virtual spaghetti.
Comments
Besides that you still did not answer why you replaced the moving average code by your prevCycle1-prevCycle8 stuff?! Does it really add so much value so it makes sense to replace the more efficient code?
Even if it's important to have the exact history of cycles, it could be solved better:
Use the destination- and source-part of the mov, add and sub instructions as pointers which tell you where to store/find the subtract - value (search for ring-buffer in wiki).
Subtract the last in the history from the average
move the last-pointer one forward (like you do it in a ring-buffer)
add the current value to the average
mov the current to the RAM to which first-pointer points
move the first-pointer forward (like you do it in a ring-buffer)
These are only a small amount of instructions compared to your 8 mov's, 8 add's:
1 sub, 1 add to pointer, 1 reset to begin of ring buffer, 1 add, 1 mov, 1 add to pointer, 1 reset to begin of ring buffer -> 7 instructions
Your "shl avgCycle, 1" does not divide by 8, it divides by something else, as
1. 1 should be #1
and 2. #1 should be #3 for a division by 8
Could you replace it with a multiply algorithm and compare the results?
There are other unlisted instructions, but they are probably not implemented.
I caught the shl typo, so that is fixed, and have removed the MUL and put what I think is a substitution that will work.
My extra code and cycles and inefficiency is not important at all, except while I am trying to feel my way through this it literally makes sense and logically makes sense to me and is easy to troubleshoot when I am following it. The code you wrote will be a much better option when I feel more confident in the program and my understanding of PASM.
The code below has me confused, because I'm not sure how dontcount can be a static number, if I understand it correctly. So I went with the literal inefficient way, until I get better. I know your way is best, I'm just taking steps to getting to the point where I know your way is best and I understand why it is best.
I replaced it with the following
The reason I thought the second one to be a better way, is because it allows you to compare the current to the previous 8's average, and if the current is garbage, throw it out and replace it with the average value, rather than pollute the average with garbage until it cycles out. What makes the first way superior to this?
and
Otherwise you have the average of the previous calculation + 8 actual values and divide this by 8 which could explain the offset.
So, the second try is better as it at least removes prevCycle9 which is usually somewhere around the average calculated before.
Do you have a filter for the big gap? If not this could also add an offset.
With the first code block, I subtract the oldest value, leaving the remaining 7 and then add the newest value and average it with a divide by 8, this code is a typical circular buffer.
For the second code block (not all of these steps are done in that block), I have a filter that verifies the size is not outside of the 90% to 110% of the average. If it is not outside of the average, it uses the newest value and considers it valid and calculates based on that single valid value, if it is outside of the 90-110% range, it overwrites it by replacing it with the calculated average value and calculates off of that until the next time through the loop. This code is a validate the value, based on the average of the previous 8, and compute off of it as long as it is within a preset allowable range.
I finally figured out why I cannot get it to work, I kept getting numbers that were semi close, but not correct. The low is 57% of the total and the high is 43% of the total time to go from 1 rising edge to the next.
I should be reading 266000 cycles at 500rpm, I'm reading 201000 cycles.
I need to read from rising edge to rising edge, as your original code did. After playing around with the code, I was able to get this to work somewhat, so tomorrow I will have to tweak it so that it is reliable.
There are 2 possibilities:
Either you have sumAvgCycle which keeps track of the sum of all prevCycle2-8 + lowCycle, then you only need to subtract the last value prevCycle9 and add the actual value lowCycle to get the actual sum. Then you have to copy this into avgCycle and divide this by 8 to get the average of all 8 values. This removes 6 add-instructions.
Or you clean avgCycle each time and sum it up freshly. Here it's wrong to subtract prevCycle9, as it is not part of the sum.
You code is currently a mixture.
I see what you mean now, and why you would have the removal of the 6 add instructions after the first pass through.
I can't seem to come up with a good way to sum all 8 the first time, and then take that summed value, subtract the oldest, insert the newest and then divide by 8 again during the loops after the first time.
I see how this would work well, I'm just not sure how to do this in PASM. Is there a way to bracket if statement instructions together, like you can in spin? Maybe I should have some sort of flag that indicates the sum has never been calculated, and after that ignore the "all 8" summation and just do the subtract oldest/insert newest, then divide by 8?
I think you've done something like that that with this code [edit - I see that you've initialized avgcnt with the value of 8, I think I'm getting closer to understanding]
I would guess I'm going to end up going in a circle right back to the way you coded it, which may seem pointless from the outside looking in, but by that point I'll have an intricate understanding of why you wrote that averaging code the way you did. The only difference is that it probably took you 1/2 an hour, and it's going to take me 1/2 a month
The problem now is that when I have the gap come in, which is approximately 2.5 times the length of a normal rising edge to rising edge, it throws off the average. Should I filter that out somehow? Maybe if the cycle value is larger than 1.75 times the average from the previous loop, consider it an anomaly and toss it?
I don't understand Yours thinking.
What You need average to.
My questions are.
1. How many pistons have this engine.
2. How many times Gap occur to have all pistons working (complete cycle of engine).
1.) I have a 4 cylinder and a 6 cylinder engine, but technically this is independent of this part of the project as I should be able to measure either with the same code
2.) I am trying to average the time it takes the crank trigger wheel to go from one falling edge to the next on its 36-2 crank trigger wheel
In electrical terms, the sensor puts out a variable frequency based on the speed of the engine. I have code that simulates the frequency exactly if you'd like to see it. I can mathematically calculate the engine rpm exactly based on how many clk cycles it takes to go from 1 falling edge to the next. The hard part comes when you want to allow for error correction and the "-2" portion of the trigger wheel that creates a frequency "gap" (single falling edge to falling edge measurement), that is about 2.5 times the length of a normal falling edge to falling edge measurement.
With the previous PASM code, I can calculate the cycles (rpm), but you'll see it jump around and show an incorrect number when the "gap" virtually goes past the virtual sensor (I have one prop pin feeding another) when I'm simulating this on my protoboard. Does this make sense? I can go into as much automotive detail as you'd like if I am not making sense, and draw/use some pictures.
What You need frequency to.
If I understand engines that is not necessary to know. You need only position of piston to start injection else ignition in degrees before top position of piston.
And that info give You position of tooth. But still to full engine cycle You need 2 revolutions -- That give question HOW You know start of first engine piston.
I am actually not that far yet but to answer your question, there is a trigger wheel on the cam shaft (I finally have an exact simulation of that too) and as you probably know, the camshaft rotates 1:2 compared to the bottom half of the engine. With this reference we can tell if we are on the compression stroke or the exhaust stroke of each cylinder. Right now I am trying to accurately read rpm first from this crank trigger wheel, then I'll accurately read rpm from the camshaft trigger wheel and then try and merge them together.
Maybe I should have a rolling average of 32 different samples?
I slowed the program way down so that I could watch the highs/lows slowly at only a couple of rpm, but still 3 or 4 teeth a second.
Can you explain how this command works? I know how it works in spin, but not in PASM. Is this a shortcut way of writing it? Is it "wait for pin to not equal 1, check pin number stored in masks register"
and then
"wait for pin to equal 1, check pin number stored in masks register"
Could I write?
I'm chasing an issue where the code is actually hosing 2 cycle counts instead of 1. In the code below, the elongated low and then high cycle should be treated as a single tooth, but it is being treated as two different teeth, or it is split in half as far as cycles, and the low is being combined with the preceding high, while the elongated high is then being combined with the following low. Either way, it's screwing up 2 cycles instead of one.
Any ideas on how to deal with that? I've been troubleshooting this all night, it's driving me crazy.
Instead of a measurement from the falling edge of the elongated logic low, to the falling edge of the elongated logic high.
When you switch them around to waitpne first, you then get 1 out of range value, instead of two.
When you factor in the value of the gap in a /8 situation, it diminishes the average by 20%
When you factor i the value of the gap in a /32 situation, it diminishes the average by 6%
With /8, you have an average of ~26666592 or ~33333258. 79999920 is 2.40 times the average here
With /32, you have an average of ~26666592 or ~28333258. 79999920 is 2.82 times the average here
So the question is, is the additional overhead from a divide by 32, instead of a divide by 8 worth the resolution?
6% is tolerable, but 20% is not. I could also create some sort of 1 time divide by 8 or 32 and then filter out anything above or below certain percentages?
Below is what I observed when I slowed things down.
I already told you that you either need to invert the signal or change the code accordingly, didn't I?!
WAITPNE means wait until pins are not equal and works like that:
It's reading INA (all 32 bits) and uses one of both parameters (I think the destination parameter, so the first one) to mask out all bits which are not of interest (binary AND). Mask out means that all bits are set to 0 which you are not interested in. Then it compares the result with the other parameter (the source - second one) and if they are equal it continues waiting.
WAITPEQ works the same way, but it waits as long as the bits of interest are not equal. => WAITPNE will wait, WAITEQU will continue the program
bit 0 of the result only depends on PIN 0. So, alternating the waitpne and waitpeq instructions will wait until the previous state of the pin is changed. Well ... as long as the code in between has a shorter runtime than the high- / low-time of the signal.
I do remember you saying I'd need to possibly invert the signal, however apparently I didn't comprehend the "change the code accordingly" part so I did learn the hard way! But I will say that I definitely learned a lot.
I actually have something working now, it works from 300rpm to 15000rpm and it's pretty accurate. Now, I have no idea how it is going to do with a constantly changing signal such as an engine, but I'll let you know soon
Here is the code, again I know it's a far cry from the code you wrote, but since I wrote it I'm able to troubleshoot it a little better. If it works, I may go back and try to get your code working with my hardware/signal as well. I really didn't want to do the first small code block outside of the initialize label, but I couldn't get it to work without doing that. : (
I guess it isn't too bad, because I still have the speed I need for 15,000rpm or 9000 teeth a second.
One more tipp:
You can mix wrxxxx/rdxxxx with other instructions to get a better runtime. The point is, that wr/rd-instructions need to wait for the next HUB-access. Each COG gets HUB access after max. 16 clock-cycles when it missed the right point. So, after one wr/rd-instruction immediately followed by another wr/rd-instruction, this second one for sure has to wait for 814 clock-cycles. Not to waste this time with waiting, you can add 23 other non-HUB instructions between two HUB-RAM access instructions. So, with some clever resorting of the add and the mov instructions you can make the code execution faster.
And even if you don't need the speed, faster code will result in less power consumption, as the waitxxx - instructions will really halt the COG.
PS:Changed numbers as suggested by kuroneko
I'm not sure why I add it in and then remove it, I'll have to see if there's a reason for that or not tomorrow and let you know
NOW FOR THE GOOD NEWS!!!
Thanks to everyone that has helped me and endured my frequent posts in the past month, although this project is far from done I'd like to show that I made serious progress today. See the two pictures below, I can now calculate rpm based on rising edge to rising edge of each of the teeth on my hall effect trigger wheel, as well as from gap to gap of my trigger wheel. I've tested it in the car just revving it up and driving around slowly, and I've tested it with the simulated signal up to 15000 rpm! A special thanks to Mag102, Sapieha, Duane, Average Joe, Potatohead, I really appreciate your help and never thought I'd be able to do this and without your help Andreas, I would not have been able to learn any PASM, let enough to write what I did in less than a month of learning!!! I have sooooo much PASM to learn, but I feel like I have came quite far in a few weeks thanks to the help I've received. Thank you for sticking with this project, I am very grateful and if I ever make it to Germany I will definitely be buying you a beer or two.
Good gap count (99999 is a hard coded number, same as 0)
Bad (sometimes inaccurate gap count, still accurate tooth count though)
Transactions from you are defaulted to auto-commit ;o) Thanks again for setting things straight!
@turbosupra:
Bad luck ... you found the one and only german that does not like beer ;o)
<
Another German (both parents) that does not like beer : D
Onto the actual bad luck, I'm trying to calculate time difference between when the two sensors hit TDC (crank/cam) and having a hell of a time. Oh, and divide by 10 in PASM ... no clue on that.