is equal to having a local variable which needs to be initialized before first usage. It's very important to have res definitions at the end of one PASM section with no code or data following, because it will confuse the address-counter.
The cognew for a PASM code has a different semantic. For a SPIN-cog it's pre-defined that the 2nd parameter has to point to a stack-space that can be used by the SPIN interpreter. For a PASM - COG you are the master of the second parameter. You can use it to pass values (except the 2 least significant bits which are always %00) or you pass a pointer into HUB RAM that is used by the remaining SPIN-COG and the new PASM-COG for communication.
It's not the pin-number that is stored in tmp1, it's the address which points to the variable fcPin. With the readlong it actually copys the content of fcPin (HUB-RAM) into tmp2 (COG-RAM).
The ANDN is deleting all bits which are set in mask - in this case only the bit representing the input. Actually this step is not needed, because it's guaranteed that all PINs are input for the COG when starting it.
Adding 4 simply forwards the pointer tmp1 which pointed to fcPin to the next variable in HUB-RAM (fcCycles). That's where the COg wants to store the result of the measurement (the length of a whole cycled in clockticks).
The wrlong is copying the value from COG-RAM to HUB-RAM and the SPIN-portion can directly read it from there using fcCycles anywhere it wants.
is equal to having a local variable which needs to be initialized before first usage. It's very important to have res definitions at the end of one PASM section with no code or data following, because it will confuse the address-counter.
The cognew for a PASM code has a different semantic. For a SPIN-cog it's pre-defined that the 2nd parameter has to point to a stack-space that can be used by the SPIN interpreter. For a PASM - COG you are the master of the second parameter. You can use it to pass values (except the 2 least significant bits which are always %00) or you pass a pointer into HUB RAM that is used by the remaining SPIN-COG and the new PASM-COG for communication.
Is this why there is a shl command, to strip those 2 LSB's?
It's not the pin-number that is stored in tmp1, it's the address which points to the variable fcPin. With the readlong it actually copys the content of fcPin (HUB-RAM) into tmp2 (COG-RAM).
The ANDN is deleting all bits which are set in mask - in this case only the bit representing the input. Actually this step is not needed, because it's guaranteed that all PINs are input for the COG when starting it.
Adding 4 simply forwards the pointer tmp1 which pointed to fcPin to the next variable in HUB-RAM (fcCycles). That's where the COg wants to store the result of the measurement (the length of a whole cycled in clockticks).
The wrlong is copying the value from COG-RAM to HUB-RAM and the SPIN-portion can directly read it from there using fcCycles anywhere it wants.
I must be missing something obvious, because I still don't see how cyclepntr interacts with fcCycles anywhere in the code? What is the point of the mask? And what do you suggest I modify this object to do? Should I have all of the math done in the PASM section so that I can query the object for a value and get the return from my main code, or?
No, the shl is creating the bitmask for dira and waitpne/waitpeq
let's say pinNr is 5
%00000000_00000000_00000000_00000001 << pinNr
%00000000_00000000_00000000_00100000
The LSB being cleared means that you can only pass long alligned addresses. So, if you want to pass the address of a byte you have to be carefull and assure that this address is long alligned.
fcCycle is a variable in HUB-RAM. You can access it's content in SPIN by simply using the variable name. You already know the @ operator?! which gives you the address of a variable instead of it's content. If you know an address you can use an instruction like
long[ @fcCycle ] := 10
to set the value of the variable.
cyclepntr := @fcCycle
long[ cycleptr ] := 10
is doing the same. And that's in fact how the PASM code works! cyclepntr is a COG RAM variable which contains the address of the variable fcCycle. And the instruction
wrlong cycle, cyclepntr
is the equivalent of long[cycleptr] := cycle.
I already did some changes to the code. First of all a problem was that I read a lot of zeros in the output. Problem was that jm_frequin deleted the fcCycle after reading! So I removed this part.
jm_frequin was programmed in a way that it only measured each 2nd cycle. I changed it to measure each cycle and for example update another variable which counts theeth.
If you want you can add some code which directly calculates the frequency and stores it in another variable.
You can also add the code for calculating the moving average and store it in another variable.
By the way ... thank you for the screenshots ... very interesting! Do you already know how to read the signal with a propeller?
I'd try it with a diode which only let's the positive signal through and a resistor limiting the 5V. But maybe someone more experienced can jump in here and give advice on this.
It would be interesting to see more details - measuring the signal with 1ms per division resolution instead of 20ms per division.
I still don't see how fcCycles in spin is related to Cycles in pasm. I don't see any direct correlation in the code? Is "fc" an alias that points to a pasm variable of the same name?
I am actually the one that set fcCycle to 0, I did this so I knew that I was getting one of 2 things when I called the .freq object, a new value or a 0 and in my code I would just filter out the 0's.
I tried to add the code for calculating the moving average inside of the dat and didn't even get past the first step . I tried to add "wrlong (80000000/cycles), rpmptr " to the dat code of stepbystep but I could not get it to do any division? A search on division pasm did not yield any usable results either. Maybe some help with psuedo code would get me going?
No, the shl is creating the bitmask for dira and waitpne/waitpeq
let's say pinNr is 5
%00000000_00000000_00000000_00000001 << pinNr
%00000000_00000000_00000000_00100000
The LSB being cleared means that you can only pass long alligned addresses. So, if you want to pass the address of a byte you have to be carefull and assure that this address is long alligned.
fcCycle is a variable in HUB-RAM. You can access it's content in SPIN by simply using the variable name. You already know the @ operator?! which gives you the address of a variable instead of it's content. If you know an address you can use an instruction like
long[ @fcCycle ] := 10
to set the value of the variable.
cyclepntr := @fcCycle
long[ cycleptr ] := 10
is doing the same. And that's in fact how the PASM code works! cyclepntr is a COG RAM variable which contains the address of the variable fcCycle. And the instruction
wrlong cycle, cyclepntr
is the equivalent of long[cycleptr] := cycle.
I already did some changes to the code. First of all a problem was that I read a lot of zeros in the output. Problem was that jm_frequin deleted the fcCycle after reading! So I removed this part.
jm_frequin was programmed in a way that it only measured each 2nd cycle. I changed it to measure each cycle and for example update another variable which counts theeth.
If you want you can add some code which directly calculates the frequency and stores it in another variable.
You can also add the code for calculating the moving average and store it in another variable.
I'll try and pull out the scope and get a 1mS reading. I believe I used a 3.9k and 10k resistor ladder, and a forward biased diode to try and read the sensor, but I didn't have the code capable of computing it.
Would you like any other readings while I'm 'under the hood' ?
By the way ... thank you for the screenshots ... very interesting! Do you already know how to read the signal with a propeller?
I'd try it with a diode which only let's the positive signal through and a resistor limiting the 5V. But maybe someone more experienced can jump in here and give advice on this.
It would be interesting to see more details - measuring the signal with 1ms per division resolution instead of 20ms per division.
Bitmask: correct, it's for masking out all the bits not needed or that should not be touched during an operation or for clearing/setting particular bits. So you can use a bitmask for single or multiple bits.
For example: You attached an 8 bit ADC to PINs 8-15 on the propeller. Using INA in PASM will always give you the status of all 32 I/Os. So you'd use a mask $0000_FF00 to make all bits 0 which you are not interested in:
mov reading, ina
and reading, adc_mask
shr reading, #8
With this sequence you have a value in the range from 0 to 255 as you'd expect it from an 8 bit ADC.
SPIN - part:
============
long fcPin ' here it's important to keep the order because only one pointer can
' be passed to PASM
long fcCycles
long fcToothcount
...
okay := cog := cognew(@frcntr, @fcPin) + 1 ' starting COG with frcntr and passing the address of the
' variable fcPin
PASM - part:
============
frcntr mov tmp1, par ' here the PASM stores the address of fcPin in tmp1
' ( this is like tmp1 := @fcPin in SPIN)
rdlong tmp2, tmp1 ' here the content (the pin number) of the variable
' is read and stored in tmp2 ( like tmp2 := fcPin )
...
add cyclepntr, tmp1 ' here 4 is added to the address of fcPin which is
' where you find fcCycles, as fcCycles comes
' directly after fcPin in the SPIN variable definition.
' So indirectly you have cyclepntr := @fcCycles
...
wrlong cycles, cyclepntr ' this will write the content of cycles to the memory
' that cyclepntr points to. So, here you have
' long[ cyclepntr ] := cycles
...
cyclepntr long 4 ' this is predefined with 4 because this is the
' address-offset needed to calculate address of the
' next variable
tmp1 res 1
I'd opt for using a moving average that averages 4,8 or 16 values (2^y), because then you get the division for free. You can simply use a shift operation. If you insist on a division by any other value you have to find some code which divides by any divisor.
Ok, the light bulb is going off above my head, I think I have this.
With pasm's cognew, you can pass the address to the pasm method and one address of a variable which acts as a starting point on a virtual map. To interact with spin and access other parts of the virtual map, you skew the address by X amount of longs and this completes the indirect assignment? If I have this correct, I would have NEVER figured that out on my own so thank you! And for confirming my bit mask understanding.
This pasm division thing is going to take me all day, and that's if I'm lucky, so I'll get to work on that now
Ok, at least I can probably provide some comedy with this code. We'll call this ts_brokein.spin
I cannot get my pub rpm function to work, I thought that might be a good starting point, and I'm not sure although I think it is the part where I try to define where rpmpntr is in memory.
I'm also not sure you can even do math in pasm the way my commented out portion is trying to do it.
{{
This object uses ctra and ctrb of its own cog to measure the period of an intput waveform.
The period is measured in clock ticks; this value can be divided into the Propeller clock
frequecy to determine the frequency of the input waveform. In application the period is
divided into 10x the clock frequency to increase the resolution to 0.1Hz; this is espeically
helpful for low frequencies. Estimated range is 0.5Hz to ~40MHz (using 80MHz clkfreq).
The counters are setup such that ctra measures the high phase of the input and ctrb measures
low phase. Measuring each phase independently allows the input waveform to be asymmetric.
In order to prevent a loss of signal from causing an eroneous value from the .freq() method
the fcCyles value is cleared after a valid frequency is calculated; this means that you
should not call this method at a rate faster than the expected input frequency.
}}
var
long cog
long fcPin ' frequency counter pin
long fcCycles ' frequency counter cycles
long rpmCycles
pub init(p) : okay
'' Start frequency counter on pin p
'' -- valid input pins are 0..27
if p < 28 ' protect rx, tx, i2c
fcPin := p
fcCycles := 0
okay := cog := cognew(@frcntr, @fcPin) + 1
else
okay := false
pub cleanup
'' Stop frequency counter cog if running
if cog
cogstop(cog~ - 1)
pub period
'' Returns period of input waveform
'return fcCycles
result := fcCycles
fcCycles~
pub freq | p, f
'' Converts input period to frequency
'' -- returns frequency in 0.1Hz units (1Hz = 10 units)
'' -- should not be called faster than expected minimum input frequency
p := period
if p
f := clkfreq * 10 / p ' calculate frequency
fcCycles := 0 ' clear for loss of input
else
f := 0
return f
pub rpm
'return rpmCycles
result := rpmCycles
dat
org 0
frcntr mov tmp1, par ' start of structure
rdlong tmp2, tmp1 ' get pin address location
rdlong tmp3, par ' get beginning variable address location
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 ' adds val of temp 2, which is the address of par to ctrb
mov frqb, #1
mov mask, #1 ' create pin mask
shl mask, tmp2
andn dira, mask ' input in this cog
add tmp1, #4
mov cyclepntr, tmp1 ' save address of hub storage for spin variable fcCycles
add tmp3, #8
mov rpmpntr, tmp3 ' save address of hub storage for spin variable rpmCycles
restart waitpne mask, mask ' wait for 0 phase
mov phsa, #0 ' clear high phase counter
highphase waitpeq mask, mask ' wait for pin == 1
mov phsb, #0 ' clear low phase counter
lowphase waitpne mask, mask ' wait for pin == 0
mov cycles, phsa ' capture high phase cycles
endcycle waitpeq mask, mask ' let low phase finish
add cycles, phsb ' add low phase cycles
wrlong cycles, cyclepntr ' update hub
{mov previousCycles7, previousCycles6
mov previousCycles6, previousCycles5
mov previousCycles5, previousCycles4
mov previousCycles4, previousCycles3
mov previousCycles3, previousCycles2
mov previousCycles2, previousCycles1
mov previousCycles1, cyclepntr
mov cycleAverage, cyclepntr} '((previousCycles7+previousCycles6+previousCycles5+previousCycles4+previousCycles3+previousCycles2+cycles)/8)
wrlong cycles, rpmpntr
jmp #restart
' --------------------------------------------------------------------------------------------------
POS_DETECT long %01000 << 26
NEG_DETECT long %01100 << 26
tmp1 res 1
tmp2 res 1
tmp3 res 1
mask res 1 ' mask for frequency input pin
cyclepntr res 1 ' hub address of cycle count
cycles res 1 ' cycles in input period
rpmpntr res 1
cycleAverage res 1
previousCycles7 res 1
previousCycles6 res 1
previousCycles5 res 1
previousCycles4 res 1
previousCycles3 res 1
previousCycles2 res 1
previousCycles1 res 1
fit 492
Here are my latest changes to the code, enjoy - actually I did enjoy writing it. But maybe you want to do more by yourself? But first your code:
You nearly got adding another variable right! The only problem is in the very beginning:
frcntr mov tmp1, par ' start of structure
rdlong tmp2, tmp1 ' get pin address location
rdlong tmp3, par ' get beginning variable address location
What you really want to do with tmp3 is the same as you do with tmp1 which is a MOV and not a RDLONG. So, if you change it to
mov tmp3, par
it would work.
BUUUUUT ... there is no reason for adding a tmp3 at all! You can use tmp1 again:
add tmp1, #4
mov rpmpntr, tmp1
you have to become aware of those little differences, as the COG-RAM is limited to 496 instructions/variables. That's not too much!
You can also have a closer look to my code. I do initialize all the variables which later hold the pointers with the offset. Then I only have to add - not add + move. This does not save memory because I had to switch the pointer variables from res to long, but it saves runtime - and I like it ;o)
Next step of optimization could be to write all variables back in a bulk-transfer. Then we don't have 10 pointers to 10 different variables but a simple loop which would copy all variables from COG-RAM to HUB-RAM. Maybe as an exercise?
The code you commented out would work, but it's only moving stuff and not calculating something.
The averaging I did is using the method mentioned by Beau in the thread I linked some posts earlier.
Good evening sir! I thought it was about time to reward myself with an avatar, I mean I am learning PASM now
Ok, before I look at your code (thank you kindly for the code), I would like to get the following to work as a learning exercise:
pub rpm
'return rpmCycles
result := fcCycles
I've tried every single combo I can think of to just get jm_freq.rpm to return the same values that jm_freq.period returns and I cannot, for the life of me get it to work. I also tried mov tmp3, par somewhere in my hour of attempts, but I have something else wrong because it didn't work. I have a feeling I'm running all around what I want to do, but missing the exact spot.
My biggest uncertainty is still how cog ram interacts with hub ram (I've been doing some reading today, so I believe I'm using the right terminology) . Can you confirm if this is correct?
cognew(@frcntr, @fcPin) allows the second parameter, @fcPin to define a starting address, correct?
add tmp1, #4
mov cyclepntr, tmp1 directs the code to assign the address of par plus 4 bytes (a long) and then assign that address to cyclepntr, correct?
since in the spin var section, fcCycles is the long immediately following the long fcPin or our par address, this is an indirect memory address assignment?
var
long cog
long fcPin ' frequency counter pin
long fcCycles ' frequency counter cycles
long rpmCycles
So I can now do an additional 32 bit shift to indirectly assign rpmpntr to the next long listed in the VAR section by adding another #4 (literal 4 bytes) to the new value of tmp1, which is now currently the address location for fcCycles?
add tmp1, #4 ' tmp1 := address location of tmp1 + a literal 32 bits
mov rpmpntr, tmp3 ' save address of hub storage for spin variable rpmCycles, rpmpntr := tmp3
Sorry this is redundant, I just don't want to build on a misunderstanding and I'm not confident I understood well before, because I feel like I should have been able to get jm_freq.rpm to return the value of fcCycles before if I had understood well, and I could not get it to work.
wrxxxx copies a value from COG RAM to HUB RAM (xxxx being byte, word or long)
rdxxxx copies a value from HUB RAM to COG RAM
Both instructions need a COG RAM address (1st parameter) and a HUB RAM address (2nd parameter). The HUB RAM address is a byte address, as HUB RAM is organized in bytes. But for moving a word the address needs to be alligned to word addresses, for moving longs it needs to be alligned to long addresses. For passing values from one COG to the other both need to know where to read from/write to and that's what the COG that fires up another COG tells it via the 2nd parameter of the COGNEW. It passes the address of the first variable to be used.
For me it looks like you got the point!
And I don't see a reason why the code from post #70 should not work with just that little change (make rdlong a mov).
It works now, I had called the wrong object and was "chasing my tail". Thanks for the confirmation.
I looked at your code, it works awesome and will take me some time to try and digest before I can make an attempt at the bulk transfer loop. I might also try and have it capture the amount of gaps it detects per second.
Oh, by the way ... I think what you really want is to reset the tooth-counter if you detect a gap. That has to be changed as well. Currently it simply goes round and round and whichever tooth it detects first will be number one.
I also think that the counter sould only go up to 33 according to the picture of the wheel.
Oh, by the way ... I think what you really want is to reset the tooth-counter if you detect a gap. That has to be changed as well. Currently it simply goes round and round and whichever tooth it detects first will be number one.
I also think that the counter sould only go up to 33 according to the picture of the wheel.
What I mean is that each tooth on the wheel has a fixed number, right? So, no matter when you start the motor, tooth 1 will be tooth 1 - always and forever. You gave the reason why: A certain tooth will fire up the spark for a certain cylinder, right?
The current code simply starts counting from 1 when you start the COG, no matter which tooth really passes by. The code needs to be changed in a way that it starts counting teeth after seeing the gap passing by once. Then you have a 1 to 1 relation between the tooth passing by with the tooth-number in the COG.
At least this is my current understanding in a matter I have no experience with so far ;o)
This thread somehow reminds me on a german saying:
The blind helps the deaf.
;o)
Me not knowing much about the problem domain and turbo being a newb with propeller.
I enjoy !
As You know engine need always know what piston need be fired.
To that You need always start counting from Gap.
And in same time accumulate Gap counts for RPM
What I mean is that each tooth on the wheel has a fixed number, right? So, no matter when you start the motor, tooth 1 will be tooth 1 - always and forever. You gave the reason why: A certain tooth will fire up the spark for a certain cylinder, right?
The current code simply starts counting from 1 when you start the COG, no matter which tooth really passes by. The code needs to be changed in a way that it starts counting teeth after seeing the gap passing by once. Then you have a 1 to 1 relation between the tooth passing by with the tooth-number in the COG.
At least this is my current understanding in a matter I have no experience with so far ;o)
This thread somehow reminds me on a german saying:
The blind helps the deaf.
;o)
Me not knowing much about the problem domain and turbo being a newb with propeller.
I enjoy !
You are correct, and doing pretty well to understand the automotive concepts. Tooth 1 is always tooth one and that is determined by the gap, which is a tooth counting reset. I would have to confirm this, but let's say that on my 4 cylinder engine, cylinder 1 fires at tooth 9, cylinder 2 fires at tooth 18, cylinder 3 fires at tooth 27, cylinder 4 fires at tooth 36 (which tooth 36 technically doesn't exist, so there is probably some sort of offset on my tooth numbers). Both fuel and timing are going to need to be activated for that ignition and fuel injectors appropriate cylinder and the rpm can be counted by determining gaps/bridges per minute. This could probably be counted just from rising edge to rising edge, but the bridge/gap secondary way of doing it allows for a less dynamic measurement. This is a simplistic version of how the ecu uses those sensors to control the engine. I also enjoy this, if only I lived in Hessen or you lived in Maryland, we could start our own car company! A propeller powered car
My 6 cylinder actually has the 12 tooth wheel I posted a picture of, which to my surprise has no gap/bridge ! I believe it determines which tooth represents which cylinder by also reading the camshaft position sensor, but more testing would be required for me to confirm this.
I'm guessing there is a way to do this, I just have not been able to successfully do the following yet. 10000 rpm is 10000 bridge/gaps per minute or 166.7 bridge/gaps per second. I was trying to count those bridge/gaps per second and display the peak, as a sort of secondary way of measuring rpm.
What I mean is that each tooth on the wheel has a fixed number, right? So, no matter when you start the motor, tooth 1 will be tooth 1 - always and forever. You gave the reason why: A certain tooth will fire up the spark for a certain cylinder, right?
The current code simply starts counting from 1 when you start the COG, no matter which tooth really passes by. The code needs to be changed in a way that it starts counting teeth after seeing the gap passing by once. Then you have a 1 to 1 relation between the tooth passing by with the tooth-number in the COG.
At least this is my current understanding in a matter I have no experience with so far ;o)
This thread somehow reminds me on a german saying:
The blind helps the deaf.
;o)
Me not knowing much about the problem domain and turbo being a newb with propeller.
I enjoy !
A couple of questions if I could ... what is the difference between
tmp1 res 4
and
tmp1 long 4
When you initialize the variables, you initialize them to hub ram memory address, correct? They do not need to be set to 0 because the hub ram global variable is initialized to 0 by the compiler?
Should I have it try and write in bulk via an array? Or maybe a
wrlong cycles, par
wrlong tooth, par + 4
wrlong average, par + 8
You can also have a closer look to my code. I do initialize all the variables which later hold the pointers with the offset. Then I only have to add - not add + move. This does not save memory because I had to switch the pointer variables from res to long, but it saves runtime - and I like it ;o)
Next step of optimization could be to write all variables back in a bulk-transfer. Then we don't have 10 pointers to 10 different variables but a simple loop which would copy all variables from COG-RAM to HUB-RAM. Maybe as an exercise?
First of all ... ehm ... my guess would be that your 4 cylinder fires at 1, 10, 19, 28. To fire somewhere inside of the gap would be stupid, as you'd have to "create" those teeth via software.
The RES is only reserving COG-RAM memory addresses. These memory locations are not initialized with usefull values. The advantage is that these also don't need HUB-RAM.
And the number behind RES is not a value, it's the number of long - addresses you want to reserve.
With temp1 RES 4 you could for example say something like:
mov temp1, #0
mov temp1+1, #10
mov temp1+2, par
rdlong trmp1+3, temp1+2
temp1 long 4
needs HUB-RAM, as the compiler really stores a 4 at the respective memory location. After loading the PASM code into COG-RAM you will have this position initialized as well.
No, that's not what I meant with bulk write. A bulk write means that all variables where you want to write to (HUB-RAM) are placed one after the other. The variables in COG-RAM are also placed one after the other. In this case you don't need xxxxptr variables for all. And you can do the write in a loop which only needs one wrxxxxx instruction instead of one wrxxxxx per variable. So, from a certain amount of variables is saves COG-RAM.
By the way
wrlong tmp1, par + 4
will compile, but it won't do what you expect!
One important thing is to find out what the pulse / pause ratio will be when reading the real signal. It will tell us how much instructions we can put into the pulse and how much we can put into the pause-part of the code. The current crank simulation creates a 50/50 signal.
Oh ... and it would be interesting which values you need for display only, for further processing and which deviations are allowed!
Be proud Mag! ... Wow did flags hurt my brain, assembly is hard!
This was very hard for me, and I'm sure I couldn't have done it outside of the constructs of your code, but I made a slight addition. I'm also unsure as to how to calculate time in assembly, for example to say
if (current time) => (last time measurement + clkfreq), copy value to valuePeak and reset value to 0, then continue
I started to, but wasn't sure so I commented that part out (starting at line 176)
What do you think? (had to attach externally because of the flash based/active x attachment system on this forum)
Yes, you are probably closer with your tooth numbers, I would have to check TDC on each cylinder and the timing offset at the particular RPM and I would be able to calculate it. It does however use varying degrees of offset, because I've seen timing range from 10 degrees BTDC (before top dead center) to 40 BTDC, all of which would not have a corresponding rising edge.
So res uses cog ram, and long uses hub ram, that makes perfect sense and would allow you to optimize either way based on the code written.
I will try and figure out the bulk loop this evening, let me know what you think of my code that I posted.
First of all ... ehm ... my guess would be that your 4 cylinder fires at 1, 10, 19, 28. To fire somewhere inside of the gap would be stupid, as you'd have to "create" those teeth via software.
The RES is only reserving COG-RAM memory addresses. These memory locations are not initialized with usefull values. The advantage is that these also don't need HUB-RAM.
And the number behind RES is not a value, it's the number of long - addresses you want to reserve.
With temp1 RES 4 you could for example say something like:
mov temp1, #0
mov temp1+1, #10
mov temp1+2, par
rdlong trmp1+3, temp1+2
temp1 long 4
needs HUB-RAM, as the compiler really stores a 4 at the respective memory location. After loading the PASM code into COG-RAM you will have this position initialized as well.
No, that's not what I meant with bulk write. A bulk write means that all variables where you want to write to (HUB-RAM) are placed one after the other. The variables in COG-RAM are also placed one after the other. In this case you don't need xxxxptr variables for all. And you can do the write in a loop which only needs one wrxxxxx instruction instead of one wrxxxxx per variable. So, from a certain amount of variables is saves COG-RAM.
By the way
wrlong tmp1, par + 4
will compile, but it won't do what you expect!
One important thing is to find out what the pulse / pause ratio will be when reading the real signal. It will tell us how much instructions we can put into the pulse and how much we can put into the pause-part of the code. The current crank simulation creates a 50/50 signal.
Oh ... and it would be interesting which values you need for display only, for further processing and which deviations are allowed!
(I will get a scope of the tooth pulse width on and off this weekend)
Ok, I'm not sure how to do the bulk write logically, with the wrlong's being tied to IF statements. I tried the following, but it does not work. I believe you were implying that I should do something along the lines of:
wrLongLoop
wrlong localvar, pntr to par
add localvar, 1
ad pntr, 1
jmp #wrLongLoop
I'm not sure how to do a repeat # of times, so I guessed with the loopCount, else it'll be endless.
mov loopCount, #10
wrlongLoop
wrlong cycles, cyclepntr
add cycles, 1
add cyclepntr, tmp1
sub loopCount, #1
cmp loopCount, #1 WZ,WC 'C = 1 if value1 < value2 'Z = 1 if value1 = value2
if_NC_OR_Z jmp #wrlongLoop ' if value 1 is not less than or equal to #1
On a positive note, I can now calculate gap/bridge based rpm ... thank you for prodding me towards PASM, it is bad@ss!
Thank you, although the credit is owed to you. 5 days ago I couldn't read a single PASM line/command, so I much appreciate the guidance and help.
I'm still not sure how you would do the bulk write with values that are only written based on a flag result, with an if_x statement? Can that be done?
Taking the " if_x wrlong " out of the equation, how does the logic below look? It doesn't work, but I think this is because of the wrlongs I can't comment out that are attached to an if_x statement.
Ok ... I already mentioned that bothe variables (the COG-RAM variables and the HUB-RAM variables need to be in a row and in the same order). So, assumning cycles is the first COG-RAM variable and cyclepntr points to the first HUB-RAM variable, you have to do it like that:
CON
BULK_NUM_OF_LONGS = 10
...
DAT
...
mov loopCount, #BULK_NUM_OF_LONGS
wrlongLoop
wrlong cycles, cyclepntr
add wrlongLoop, add1toDest
add cyclepntr, #4
djnz loopCount, #wrlongLoop
' cleanup the changes, so that everything works as before when coming back
mov wrlongLoop, resetWrlong
sub cyclepntr, #(BULK_NUM_OF_LONGS<<2)
....
resetWrlong wrlong cycles, cyclepntr
add1toDest long %1_000000000
The point is, that you do not want to increment the content of cycles, what add cycles, #1 would do. You want that the wrlong instruction uses cycles+1 as the address of the next iteration. This you have to do by using self-modifying code. The least significant 9 bits of an instruction are the source address, the bits from 9-17 contain the destination address. This is what needs to be incremented.
Cyclepntr on the other hand contains the address, so it is fine to add here. But as HUB-RAM addresses are byte-addresses, you have to add #4 to forward to the next LONG.
That's fun, isn't it?? ;o)
I did not test this code and I'm not sure if the compiler complains about #(BULK_NUM_OF_LONGS<<2), but I'm sure you'd find a solution if it would complain.
I don't understand why you want to write conditionally? Simply write again won't hurt and checking costs runtime instead of saving runtime.
Yes this fun! I can't believe how fast PASM is and I'm starting to see the connection clearer between cog ram (longs) and hub ram (bytes) .
The reason I use conditions is because of the way I'm having the values updated with the gap/bridge detection, as well as the max amount of teeth it detects, so it only updates when it sees peak values. Maybe this is the wrong way to go about it, but that is how I wrote the code and it is working, if there is a better way to write that, I'll be happy to migrate to that way.
Here is an archive of the code with the bulk write section not done yet and the variables not ordered yet which will better explain about why I used conditions.
Ok, I see ... 2 solutions:
1. Either have 2 ways of writing values to HUB-RAM. One way would be the old way with separate wrlongs, the other way would be a bulk write of the rest. You'd still use separate wrs for those values that are written conditionally and the bulk write for the others. From a certain amount of variables it will save COG-RAM.
2. Or have 2 variables for the conditional write-values. 1 will keep the valid value and will be copied to HUB-RAM periodically. The other one is the dynamic one, which is moved to the "static" variable where you currently have the conditional wr-instructions.
Then the bulk read will be fine and even if you have to duplicate 2 variables save some RAM.
And here another find:
mov tempCnt, cnt ' * copy count to tempCnt
sub tempCnt, timeStamp ' * subtract initial timeStamp from tempCnt
cmp tempCnt, delta WZ,WC 'test if delta time reached or exceeded, C should be 1 or Z should be 1
There is a trick which saves the tempCnt variable:
mov cnt, cnt ' * copy count to tempCnt
sub cnt, timeStamp ' * subtract initial timeStamp from tempCnt
cmp cnt, delta WZ,WC 'test if delta time reached or exceeded, C should be 1 or Z should be 1
This makes use of the fact that each SPR (special function register - like cnt) also has some shadow-RAM behind. This shadow RAM is used if the SPR name is used as destination.
Some fine-tuning of the code is also needed! I think it makes more sense to remove the hardcode of the dontcount-value. It has to be dynamic! Otherwise you'll get no more updates of the average if the RPM is below a certain value. (A normal tooth will exceed the dontcount.)
idle is approximately 900rpms. I will say that the bridge or gap looks morel like a gap then a bridge based on the scope, would you agree? I have not pulled the motor apart to see yet, and I know that would be the ultimate confirmation, but to me it appears as a gap or low based on the scopes readings. Let me know if you'd like to see more readings.
Comments
is equal to having data in a DAT-section.
tmp1 res 1
is equal to having a local variable which needs to be initialized before first usage. It's very important to have res definitions at the end of one PASM section with no code or data following, because it will confuse the address-counter.
The cognew for a PASM code has a different semantic. For a SPIN-cog it's pre-defined that the 2nd parameter has to point to a stack-space that can be used by the SPIN interpreter. For a PASM - COG you are the master of the second parameter. You can use it to pass values (except the 2 least significant bits which are always %00) or you pass a pointer into HUB RAM that is used by the remaining SPIN-COG and the new PASM-COG for communication.
It's not the pin-number that is stored in tmp1, it's the address which points to the variable fcPin. With the readlong it actually copys the content of fcPin (HUB-RAM) into tmp2 (COG-RAM).
The ANDN is deleting all bits which are set in mask - in this case only the bit representing the input. Actually this step is not needed, because it's guaranteed that all PINs are input for the COG when starting it.
Adding 4 simply forwards the pointer tmp1 which pointed to fcPin to the next variable in HUB-RAM (fcCycles). That's where the COg wants to store the result of the measurement (the length of a whole cycled in clockticks).
The wrlong is copying the value from COG-RAM to HUB-RAM and the SPIN-portion can directly read it from there using fcCycles anywhere it wants.
I must be missing something obvious, because I still don't see how cyclepntr interacts with fcCycles anywhere in the code? What is the point of the mask? And what do you suggest I modify this object to do? Should I have all of the math done in the PASM section so that I can query the object for a value and get the return from my main code, or?
let's say pinNr is 5
%00000000_00000000_00000000_00000001 << pinNr
%00000000_00000000_00000000_00100000
The LSB being cleared means that you can only pass long alligned addresses. So, if you want to pass the address of a byte you have to be carefull and assure that this address is long alligned.
fcCycle is a variable in HUB-RAM. You can access it's content in SPIN by simply using the variable name. You already know the @ operator?! which gives you the address of a variable instead of it's content. If you know an address you can use an instruction like
long[ @fcCycle ] := 10
to set the value of the variable.
cyclepntr := @fcCycle
long[ cycleptr ] := 10
is doing the same. And that's in fact how the PASM code works! cyclepntr is a COG RAM variable which contains the address of the variable fcCycle. And the instruction
wrlong cycle, cyclepntr
is the equivalent of long[cycleptr] := cycle.
I already did some changes to the code. First of all a problem was that I read a lot of zeros in the output. Problem was that jm_frequin deleted the fcCycle after reading! So I removed this part.
jm_frequin was programmed in a way that it only measured each 2nd cycle. I changed it to measure each cycle and for example update another variable which counts theeth.
If you want you can add some code which directly calculates the frequency and stores it in another variable.
You can also add the code for calculating the moving average and store it in another variable.
Here is my current code:
I'd try it with a diode which only let's the positive signal through and a resistor limiting the 5V. But maybe someone more experienced can jump in here and give advice on this.
It would be interesting to see more details - measuring the signal with 1ms per division resolution instead of 20ms per division.
http://en.wikipedia.org/wiki/Mask_%28computing%29
It sets 0's to individual bits?
I still don't see how fcCycles in spin is related to Cycles in pasm. I don't see any direct correlation in the code? Is "fc" an alias that points to a pasm variable of the same name?
I am actually the one that set fcCycle to 0, I did this so I knew that I was getting one of 2 things when I called the .freq object, a new value or a 0 and in my code I would just filter out the 0's.
I tried to add the code for calculating the moving average inside of the dat and didn't even get past the first step . I tried to add "wrlong (80000000/cycles), rpmptr " to the dat code of stepbystep but I could not get it to do any division? A search on division pasm did not yield any usable results either. Maybe some help with psuedo code would get me going?
Would you like any other readings while I'm 'under the hood' ?
For example: You attached an 8 bit ADC to PINs 8-15 on the propeller. Using INA in PASM will always give you the status of all 32 I/Os. So you'd use a mask $0000_FF00 to make all bits 0 which you are not interested in:
mov reading, ina
and reading, adc_mask
shr reading, #8
With this sequence you have a value in the range from 0 to 255 as you'd expect it from an 8 bit ADC.
I'd opt for using a moving average that averages 4,8 or 16 values (2^y), because then you get the division for free. You can simply use a shift operation. If you insist on a division by any other value you have to find some code which divides by any divisor.
With pasm's cognew, you can pass the address to the pasm method and one address of a variable which acts as a starting point on a virtual map. To interact with spin and access other parts of the virtual map, you skew the address by X amount of longs and this completes the indirect assignment? If I have this correct, I would have NEVER figured that out on my own so thank you! And for confirming my bit mask understanding.
This pasm division thing is going to take me all day, and that's if I'm lucky, so I'll get to work on that now
I cannot get my pub rpm function to work, I thought that might be a good starting point, and I'm not sure although I think it is the part where I try to define where rpmpntr is in memory.
I'm also not sure you can even do math in pasm the way my commented out portion is trying to do it.
Here are my latest changes to the code, enjoy - actually I did enjoy writing it. But maybe you want to do more by yourself? But first your code:
You nearly got adding another variable right! The only problem is in the very beginning: What you really want to do with tmp3 is the same as you do with tmp1 which is a MOV and not a RDLONG. So, if you change it to
mov tmp3, par
it would work.
BUUUUUT ... there is no reason for adding a tmp3 at all! You can use tmp1 again: you have to become aware of those little differences, as the COG-RAM is limited to 496 instructions/variables. That's not too much!
You can also have a closer look to my code. I do initialize all the variables which later hold the pointers with the offset. Then I only have to add - not add + move. This does not save memory because I had to switch the pointer variables from res to long, but it saves runtime - and I like it ;o)
Next step of optimization could be to write all variables back in a bulk-transfer. Then we don't have 10 pointers to 10 different variables but a simple loop which would copy all variables from COG-RAM to HUB-RAM. Maybe as an exercise?
The code you commented out would work, but it's only moving stuff and not calculating something.
The averaging I did is using the method mentioned by Beau in the thread I linked some posts earlier.
Ok, before I look at your code (thank you kindly for the code), I would like to get the following to work as a learning exercise:
I've tried every single combo I can think of to just get jm_freq.rpm to return the same values that jm_freq.period returns and I cannot, for the life of me get it to work. I also tried mov tmp3, par somewhere in my hour of attempts, but I have something else wrong because it didn't work. I have a feeling I'm running all around what I want to do, but missing the exact spot.
My biggest uncertainty is still how cog ram interacts with hub ram (I've been doing some reading today, so I believe I'm using the right terminology) . Can you confirm if this is correct?
cognew(@frcntr, @fcPin) allows the second parameter, @fcPin to define a starting address, correct?
add tmp1, #4
mov cyclepntr, tmp1 directs the code to assign the address of par plus 4 bytes (a long) and then assign that address to cyclepntr, correct?
since in the spin var section, fcCycles is the long immediately following the long fcPin or our par address, this is an indirect memory address assignment?
So I can now do an additional 32 bit shift to indirectly assign rpmpntr to the next long listed in the VAR section by adding another #4 (literal 4 bytes) to the new value of tmp1, which is now currently the address location for fcCycles?
Sorry this is redundant, I just don't want to build on a misunderstanding and I'm not confident I understood well before, because I feel like I should have been able to get jm_freq.rpm to return the value of fcCycles before if I had understood well, and I could not get it to work.
rdxxxx copies a value from HUB RAM to COG RAM
Both instructions need a COG RAM address (1st parameter) and a HUB RAM address (2nd parameter). The HUB RAM address is a byte address, as HUB RAM is organized in bytes. But for moving a word the address needs to be alligned to word addresses, for moving longs it needs to be alligned to long addresses. For passing values from one COG to the other both need to know where to read from/write to and that's what the COG that fires up another COG tells it via the 2nd parameter of the COGNEW. It passes the address of the first variable to be used.
For me it looks like you got the point!
And I don't see a reason why the code from post #70 should not work with just that little change (make rdlong a mov).
I looked at your code, it works awesome and will take me some time to try and digest before I can make an attempt at the bulk transfer loop. I might also try and have it capture the amount of gaps it detects per second.
I also think that the counter sould only go up to 33 according to the picture of the wheel.
Tooth are for spark and gap are for RPM.
What I mean is that each tooth on the wheel has a fixed number, right? So, no matter when you start the motor, tooth 1 will be tooth 1 - always and forever. You gave the reason why: A certain tooth will fire up the spark for a certain cylinder, right?
The current code simply starts counting from 1 when you start the COG, no matter which tooth really passes by. The code needs to be changed in a way that it starts counting teeth after seeing the gap passing by once. Then you have a 1 to 1 relation between the tooth passing by with the tooth-number in the COG.
At least this is my current understanding in a matter I have no experience with so far ;o)
This thread somehow reminds me on a german saying:
The blind helps the deaf.
;o)
Me not knowing much about the problem domain and turbo being a newb with propeller.
I enjoy !
As You know engine need always know what piston need be fired.
To that You need always start counting from Gap.
And in same time accumulate Gap counts for RPM
You are correct, and doing pretty well to understand the automotive concepts. Tooth 1 is always tooth one and that is determined by the gap, which is a tooth counting reset. I would have to confirm this, but let's say that on my 4 cylinder engine, cylinder 1 fires at tooth 9, cylinder 2 fires at tooth 18, cylinder 3 fires at tooth 27, cylinder 4 fires at tooth 36 (which tooth 36 technically doesn't exist, so there is probably some sort of offset on my tooth numbers). Both fuel and timing are going to need to be activated for that ignition and fuel injectors appropriate cylinder and the rpm can be counted by determining gaps/bridges per minute. This could probably be counted just from rising edge to rising edge, but the bridge/gap secondary way of doing it allows for a less dynamic measurement. This is a simplistic version of how the ecu uses those sensors to control the engine. I also enjoy this, if only I lived in Hessen or you lived in Maryland, we could start our own car company! A propeller powered car
My 6 cylinder actually has the 12 tooth wheel I posted a picture of, which to my surprise has no gap/bridge ! I believe it determines which tooth represents which cylinder by also reading the camshaft position sensor, but more testing would be required for me to confirm this.
I'm guessing there is a way to do this, I just have not been able to successfully do the following yet. 10000 rpm is 10000 bridge/gaps per minute or 166.7 bridge/gaps per second. I was trying to count those bridge/gaps per second and display the peak, as a sort of secondary way of measuring rpm.
@Saphieha, thanks for joining!
When you initialize the variables, you initialize them to hub ram memory address, correct? They do not need to be set to 0 because the hub ram global variable is initialized to 0 by the compiler?
Should I have it try and write in bulk via an array? Or maybe a
Is that what you meant?
The RES is only reserving COG-RAM memory addresses. These memory locations are not initialized with usefull values. The advantage is that these also don't need HUB-RAM.
And the number behind RES is not a value, it's the number of long - addresses you want to reserve.
With temp1 RES 4 you could for example say something like:
mov temp1, #0
mov temp1+1, #10
mov temp1+2, par
rdlong trmp1+3, temp1+2
temp1 long 4
needs HUB-RAM, as the compiler really stores a 4 at the respective memory location. After loading the PASM code into COG-RAM you will have this position initialized as well.
No, that's not what I meant with bulk write. A bulk write means that all variables where you want to write to (HUB-RAM) are placed one after the other. The variables in COG-RAM are also placed one after the other. In this case you don't need xxxxptr variables for all. And you can do the write in a loop which only needs one wrxxxxx instruction instead of one wrxxxxx per variable. So, from a certain amount of variables is saves COG-RAM.
By the way
wrlong tmp1, par + 4
will compile, but it won't do what you expect!
Oh ... and it would be interesting which values you need for display only, for further processing and which deviations are allowed!
This was very hard for me, and I'm sure I couldn't have done it outside of the constructs of your code, but I made a slight addition. I'm also unsure as to how to calculate time in assembly, for example to say
if (current time) => (last time measurement + clkfreq), copy value to valuePeak and reset value to 0, then continue
I started to, but wasn't sure so I commented that part out (starting at line 176)
What do you think? (had to attach externally because of the flash based/active x attachment system on this forum)
So res uses cog ram, and long uses hub ram, that makes perfect sense and would allow you to optimize either way based on the code written.
I will try and figure out the bulk loop this evening, let me know what you think of my code that I posted.
Ok, I'm not sure how to do the bulk write logically, with the wrlong's being tied to IF statements. I tried the following, but it does not work. I believe you were implying that I should do something along the lines of:
wrLongLoop
wrlong localvar, pntr to par
add localvar, 1
ad pntr, 1
jmp #wrLongLoop
I'm not sure how to do a repeat # of times, so I guessed with the loopCount, else it'll be endless.
On a positive note, I can now calculate gap/bridge based rpm ... thank you for prodding me towards PASM, it is bad@ss!
Nice to see you're progressing.
I'm still not sure how you would do the bulk write with values that are only written based on a flag result, with an if_x statement? Can that be done?
Taking the " if_x wrlong " out of the equation, how does the logic below look? It doesn't work, but I think this is because of the wrlongs I can't comment out that are attached to an if_x statement.
The point is, that you do not want to increment the content of cycles, what add cycles, #1 would do. You want that the wrlong instruction uses cycles+1 as the address of the next iteration. This you have to do by using self-modifying code. The least significant 9 bits of an instruction are the source address, the bits from 9-17 contain the destination address. This is what needs to be incremented.
Cyclepntr on the other hand contains the address, so it is fine to add here. But as HUB-RAM addresses are byte-addresses, you have to add #4 to forward to the next LONG.
That's fun, isn't it?? ;o)
I did not test this code and I'm not sure if the compiler complains about #(BULK_NUM_OF_LONGS<<2), but I'm sure you'd find a solution if it would complain.
I don't understand why you want to write conditionally? Simply write again won't hurt and checking costs runtime instead of saving runtime.
The reason I use conditions is because of the way I'm having the values updated with the gap/bridge detection, as well as the max amount of teeth it detects, so it only updates when it sees peak values. Maybe this is the wrong way to go about it, but that is how I wrote the code and it is working, if there is a better way to write that, I'll be happy to migrate to that way.
Here is an archive of the code with the bulk write section not done yet and the variables not ordered yet which will better explain about why I used conditions.
1. Either have 2 ways of writing values to HUB-RAM. One way would be the old way with separate wrlongs, the other way would be a bulk write of the rest. You'd still use separate wrs for those values that are written conditionally and the bulk write for the others. From a certain amount of variables it will save COG-RAM.
2. Or have 2 variables for the conditional write-values. 1 will keep the valid value and will be copied to HUB-RAM periodically. The other one is the dynamic one, which is moved to the "static" variable where you currently have the conditional wr-instructions.
Then the bulk read will be fine and even if you have to duplicate 2 variables save some RAM.
And here another find: There is a trick which saves the tempCnt variable: This makes use of the fact that each SPR (special function register - like cnt) also has some shadow-RAM behind. This shadow RAM is used if the SPR name is used as destination.
Some fine-tuning of the code is also needed! I think it makes more sense to remove the hardcode of the dontcount-value. It has to be dynamic! Otherwise you'll get no more updates of the average if the RPM is below a certain value. (A normal tooth will exceed the dontcount.)
I was able to get the scope out to the car and get some samples today, I can get more with different time divisions if that will help.
The files are named to reflect the approximate rpm and time division, there names are:
idle is approximately 900rpms. I will say that the bridge or gap looks morel like a gap then a bridge based on the scope, would you agree? I have not pulled the motor apart to see yet, and I know that would be the ultimate confirmation, but to me it appears as a gap or low based on the scopes readings. Let me know if you'd like to see more readings.