I also took the opportunity to get some cam sensor readings as well, the cam sensor uses a 3 trigger wheel with teeth offset, I do not have the exact degree spacing, but I believe they are spaced approximately at 100degrees, 100 degrees and 145 degrees with about 5 degrees of width per trigger wheel. This is not exact or measured yet, but it reflects the large "gap" between the 3rd trigger wheel and the camshaft rotating around completing a full revolution before it passes the 1st trigger wheel by the sensor again.
But what I don't understand ... why don't you try to get the most information out of it? If you compare the 5000/1ms crank reading with the 5000/20ms reading, the first one is much better, right? And with a bigger sample rate it would be even better than that. But OK, most interesting so far is that it's a symetrical alternating voltage somewhere close to 5V @ 700RPM and 10V @ 5000RPM.
Just did some calculations and it looks like the idle RPM is more in 700 range.
The prop scope has 2 channels, right? Why didn't you measure the crank and the cam at once? You'd then see how both signals are related. ;o)
Did you already tell what all this efford is good for? What's your goal? Just out of curiosity .. and if you are willing/allowed to share this information.
With your question, do you mean why did I not go to the point where a single sine wave encompassed the entire prop scope screen? I did a couple of different samples because I wanted to show the bridge/gap in at least one of them since this signal is different than your average signal, in that it physically disappears after 34 cycles for 2 of the cycles. I can do a few different samples or overlay them with the second channel if you'd like, just let me know the scenarios and I'll grab them. I should have overlayed them, I just didn't think about it . 700rpm may be correct, the factory tachometer isn't known for its accuracy and I was looking at it from the passenger seats angle as well.
My ultimate goal is to be able to adjust the cam angle in relation to the crank angle, which the factory ecu currently does right now. It has an oil pressure controlled gear, of which the oil pressure is modulated by a solenoid. It can change the cam angle up to 30 degrees before or after the "0 angle" location that it would be stationary at, if the auto manufacturer had decided to not make it adjustable.
If I can get that far, there are then a few other things I would like to do in addition to that.
I have gotten the bulk write to partially work, and I'm not 100% sure why. Some of the values are working and some are not, maybe you can explain what I didn't do correctly (code is attached)?
I'm still struggling with the bulk write, I drew out a chart with cells on how cog ram maps to hub ram, to try and help myself visualize this. By the way, can I tell PASM to write to the hex address itself and not a pointer to an address?
So the longs in hub ram map to par, par + 1, par + 2, par + 3 etc, (assuming it has been coded that way) . If par was @1B, then cog ram would map to $1B - $1E? and par + 1 would map to $1F - $22, par + 2 would map to $23 - $26 etc ... is this correct? Do the numerical values at the bottom, where the variables for PASM are instantiated, represent an allocation number of bytes? Is that why they are ordered as 4, 8, 12, 16, 20, 24, 28, 32? I think this will be easier for me to understand, if I can picture in my head how the values are mapped and how those numerical columns affect things.
If so, I'm not sure why my code is not working properly, basically it is supposed to follow the logic of
cog ram long, write to par
cog ram long + 1, write to par + 4
cog ram long + 2, write to par + 8
Is this logic at least correct?
Here is the variable value definition section:
org 0
frcntr mov tmp1, par ' * start of structure, passes an address to the value par represents and copies that address to tmp1
rdlong tmp2, tmp1 ' * get beginning variable address location, copy into tmp2
mov ctra, POS_DETECT ' * ctra measures high phase
add ctra, tmp2 ' * add tmp2 to ctra and write to ctra
mov frqa, #1 ' * move frqa 1 byte?
mov ctrb, NEG_DETECT ' * ctrb measures low phase
add ctrb, tmp2 ' * add tmp2 to ctrb and write to ctrb
mov frqb, #1 ' * move frqb 1 byte?
mov mask, #1 ' * create pin mask
shl mask, tmp2 ' * shift left the mask, number of bytes of tmp2?
' save addresses of hub storage
add cyclepntr, tmp1 ' * adds value of cyclpntr and tmp1 and writes that to cyclepntr
add toothcntptr, tmp1 ' * adds value of toothcntptr and tmp1 and writes that to cyclepntr
add averageptr, tmp1 ' * adds value of averageptr and tmp1 and writes that to cyclepntr
add maxptr, tmp1 ' * adds value of maxptr and tmp1 and writes that to cyclepntr
add minptr, tmp1 ' * adds value of minptr and tmp1 and writes that to cyclepntr
' add averageptr, tmp1 ' * adds value of averageptr and tmp1 and writes that to cyclepntr
add toothCntMaxptr, tmp1 ' * adds value of minptr and tmp1 and writes that to cyclepntr
add gapCntAsmptr, tmp1
add gapCntAsmPeakptr, tmp1
mov timeStamp, cnt
The method I'm trying to get working:
mov loopCount, #BULK_NUM_OF_LONGS
mov varLongCount, #0
wrlongLoop
wrlong cycles+varLongCount, cyclepntr ' * write cycles to cyclepntr
add wrlongLoop, add1toDest ' * add 1 to the wrlongLoop value for the djnz
add cyclepntr, #4 ' * add 4 bytes since we are referring to a byte addressed hub ram value, goes to value in hub ram defined after cyclepntr
add varLongCount, #1
djnz loopCount, #wrlongLoop
' Cleanup below
mov wrlongLoop, resetWrlong
sub cyclepntr, #(BULK_NUM_OF_LONGS<<2)
resetWrlong wrlong cycles, cyclepntr
The variable name assignment:
' --------------------------------------------------------------------------------------------------
POS_DETECT long %01000 << 26
NEG_DETECT long %01100 << 26
cyclepntr long 4
toothcntptr long 8
averageptr long 12
maxptr long 16
minptr long 20
toothCntMaxPtr long 24
gapCntAsmptr long 28
gapCntAsmPeakptr long 32
maxc long 0
minc long -1
tooth long 0
toothCntMaxAsm long 0
gapCntAsm long 0
gapCntAsmPeak long 0
timeStamp long 0
delta long 80_000_000
tempCnt long 0
loopCount long 0
varLongCount long 0
database long 0
averagex
avgcnt long 8
dontcount long 13400
add1toDest long %1_000000000
tmp1 res 1
tmp2 res 1
mask res 1 ' mask for frequency input pin
cycles res 1 ' cycles in input period
fit 492
Ok ... let's explain par ...
par inside of a COG is like a readonly variable which contains the content of the second parameter of the COGNEW:
cognew( @mypasm, @first_var )
is like
par := @first_var & %11111111_11111111_11111111_11111100
This is why you'll never find a $1b here. Remember, the address has to be long alligned!
par is a pointer to HUB-RAM in our case pointing to the first variable. And even if this address has to be long alligned, it's still a HUB-RAM address which is a byte-address. So, if the first variable is a long you get to the address of the next variable by adding 4 (par + 4). The next long after that has address par + 8 ... and so on.
If you'd then have a number of words in the variable list, you'd only add 2 from one to the next word.
4, 8, 12, 16, 20, 24, 28, 32 are simply the offsets related to tmp1, which has the same value as par. The initialization at the end plus all the "add x, tmp1" instructions work like this:
cyclepntr := par + cyclepntr which is par + 4
toothcntptr := par + toothcntptr -> par + 8
...
The logic of the loop is correct!
The problems you have are:
1. wrlong cycles+varLongCount, cyclepntr ' * write cycles to cyclepntr
does not work as you expect it to work AND
2. you did not understand how to go forward to the next COG-RAM location in the wrlong
About 1.:
cycle is a label which is converted to a COG-RAM address by the compiler. varLongCount is a label which is converted to a COG-RAM address as well. cycle + varLongCount simply adds those addresses. What you expected was use the cycle address an add the content of varLongCount.
About 2.:
In PASM you have to use self-modifying code to forward addresses used by an instruction. If you have a look at an instruction on bit-level things get clearer:
iiiiii_czri_cccc_ddddddddd_sssssssss
i is the instruction opcode iteslf, so you find different values here for mov, sub, add, wrlong .....
czri are the flags that tell the COG whether the carry flag, the zero flag shall be written (WC, WZ), whether the instruction shall be readonly (WR, NR) and whether the source bits are direct addresses ( # ).
cccc are the conditional execution flags ( IF_Z, IF_NZ, IF_B ..... )
ddddddddd is the destination address - for example cycles -address would be placed here.
sssssssss is the source address
So, if you want to increment the destination-address you actually have to add
000000_0000_0000_000000001_000000000
to the instruction itself. That's why I gave you the code
add wrlongLoop, add1toDest
It should have been fully functional - no need to add the varLongCount stuff ;o) The only thing missing is a jump back to a meaningfull loop after the cleanup-code and before the definition of resetWrlong.
The cleanup restores the original instruction of wrlongLoop, so that dest points to cycles again and resets the cyclepntr.
And cycles is not in a row with all the other variables you want to write back. According to the current loop it should be the first of all the long variables in COG-RAM and not a res at the end!
par inside of a COG is like a readonly variable which contains the content of the second parameter of the COGNEW:
cognew( @mypasm, @first_var )
is like
par := @first_var & %00000000_00000000_11111111_11111100
I think I have noticed something I didn't realize before, cogram can technically only store 9 bits of data if it were a single long? And the remaining 23 bits in the register are used for other various functions? If this is correct, why is this?
I understand the value of having the hubram values in the proper order, and the corresponding cogram values also being in order but I'm still not able to make this code work completely. I'm not sure what I'm doing wrong but I'm going to try and test with it in the car rather than spend more time trying to figure out why I can't get the bulk write loop to work. Based on what you've told me, my loop is correct now and my order of longs is correct now, so it is something else that is hosing delta t, peak cnt and min. They all work when I have the separate writes though.
I just noticed you can compile and look at the memory addresses in the prop IDE, strangely enough averagex does not have a hex address? Maybe this is my problem? I also thought it was strange that min was set to -1?
" If this is correct, why is this?"
Because you want to have all informations needed to execute an instruction to fit into 32 bit. So, if you want to use direct data the only place to store it is the source address and setting the I-flag (immediate)accordingly. If you'd allow immediates with any size lager than 9 bit you'd have to occupy the next long anyway, so in terms of COG-RAM usage there is really no difference in using registered data vs. having longer immediate data. The difference is in the details of the processor internal circuitry needed. If you want to support different size immediate you need flexible instruction lenght which needs more complex logic. In fact this is what CISC processors really have - a lot of addressing modes and flexible instruction sizes...
"...rather than spend more time trying to figure out why I can't get the bulk write loop to work."
Fine, as I said bulk write is an optimization in terms of COG-RAM usage and not really needed right now as there is still some COG-RAM left for adding more code. I'll take care of it the next days.
"... averagex does not have a hex address?"
averagex and avgcnt use the same address - one memory location, different labels, where each label matches the usage of the memory location. If you have a closer look at the usage of the code, you'll see that in the beginning avgcnt is used and once we have more than 7 samples, the averagex is used. I simply implemented the moving average in a way that it only spits out an average if you have the right number of samples (=8 in this case) in the database. Otherwise you'd need a counter storing the actual number of samples and even more important, you'd really need a division for calculating the right average for the given number of samples. Fix it to 8 you don't need a counter and you can use >>3 to divide the database by 8.
Additional explaination:
mov calcavg, nopinstr ' * move calcavg to nopinstr
This actually replaces the "djnz avgcnt..." with a NOP - an operation which is doing nothing. From there on the averagex-code becomes effective for all following loops and avgcnt is no longer used.
"I also thought it was strange that min was set to -1?"
1 = $0000_0001
-1 = - ($0000_0001) = $FFFF_FFFE + 1 = $FFFF_FFFF which is the biggest number you can store in a long when using unsigned compare
( $FFFF_FFFE + 1) is the two's complement of 1 -> http://en.wikipedia.org/wiki/Two%27s_complement
@kuroneko:
Thanks for that ... nice to have you as a backup!
Woah, another epiphany! Thank you, that was a beautiful explanation and really helps me in picturing it in my head. I feel like Neo and that I have just seen the Matrix
So each cog has 512 long registers (I know a few of them are special purpose)
So each cog can work with a maximum of ~500 individual 9 bit values (possibly x 2 with some creative coding) ... if that is true, now I see why you are teaching me early on about register conservation
And somewhere around 9000 bits are available for data source and destination usage within ~500 registers per cog.
I learned 2's compliment a few years ago, I'm going to have to refresh my memory of that with the wiki link.
Honestly, I think being explained the architecture of the chip really helps you understand PASM and why it does things the way that it does, this has been very beneficial.
To be precise, you have 496 longs per COG the rest are special purpose registers. That's why PASM code usually has the
FIT 496
statement at the end. This tells the compiler that the code between ORG 0 and FIT should not extend 496 longs. Otherwise the compiler complains.
Each COG has 496 32bit registers for free usage. It does not care whether you put instructions or data there. If you use instructions you have 9 bits for a destination address and 9 bits for a source address xor a 9 bit immediate value. It's also 9 bit by purpose: the max. value a 9 bit number can store is ???? 511! Starting at 0 this makes 512 different values and make sure that each instruction can address each other register in COG RAM.
If you have stored some data in COG RAM it's your responsibility what the content means. If you want you can pack 3 x 9bit-values in one long. Or 16 x 2 bit values. It's your code which then has to deal with this kind of data.
So, if you want to count how many 9 bit values fit into a COG you have to take this into account - even if I don't really understand what this counting is good for ;o)
Hello Mag ... I finally built a small circuit and went out to test the code. : D
The prop was reading but the calculations were off and I'm trying to figure what I did wrong in my bench preparation/testing? I really thought my bench test scenarios were solid. The following screen capture is what the condition signal looks like on the prop scope and what the pst was reading in relation to that signal at that moment. I'm working on getting the voltage peaks a little closer to 3.3v, but I'm not sure that is affecting my reading of the pulses.
I think I already mentioned it somewhere (post #82 ;o) ... the point is, that
a) Crank.spin creates a symetrical signal, 50% pulse and 50% pause
b) the CrankSensor works asymetrical
restart waitpne mask, mask ' * wait for 0 phase
mov cycles, phsa ' * copies phsA to cycles
mov phsa, #0 ' * clear high phase counter
[color=red]' here we do nothing but wait for the next high[/color]
' wait for pin == 1
highphase waitpeq mask, mask ' * wait for pin to equal 1
add cycles, phsb ' * adds phsB to cycles (already containing phsA)
mov phsb, #0 ' * clear low phase counter
[color=red]' from here on we do the whole calculations[/color]
' write number of cycles measured to fcCycles
wrlong cycles, cyclepntr ' * writes cycles to cyclepntr which is really fcCycles address
' count the teeth
cmp tooth, #36 WZ,WC' *compare unsigned values of tooth and literal number 36, some sort of post fix flags?
if_ae mov tooth, #1 ' *if above or equal, move to tooth
if_b add tooth, #1 ' *if below, add 1 to tooth
.....
So, the code above implies, that the low can be shorter and that the high has to be at least long enough to do all the calculations. And here the worst case scenario counts, so all conditions are in a state that the longest possible branch is taken.
I guess what you currently have in the real setup is, that the low-phase is long and you only have high peaks which are to short.
Can you also record logic-levels with the PropScope? It would be interesting to see those with a good time-resolution, because that's what's used in the end.
Possible solution: Add an inverter to the hardware?
I would agree that they are asymmetrical (I do remember you warning me of that), but looking at the physical teeth, they are not completely out of balance to the point where it would be the tooth was 25% of the size of the gap, if that helps in giving a mental picture.
I did snap another screen shot before going to work this morning, as I remember you had told me previously that you like also seeing the shape of the wave. To me the waves look pretty close, what do you think, do you still feel there isn't enough time during the peaks? I can definitely try and inverter.
I had considered that in the final design portion of the circuit to give myself some hysteresis, but at this development point I was just hoping to test the code out. I may have to go to a schmitt trigger sooner than I anticipated though. It probably is a good idea, I was trying to start off as simple as possible.
I had considered that in the final design portion of the circuit to give myself some hysteresis, but at this development point I was just hoping to test the code out. I may have to go to a schmitt trigger sooner than I anticipated though. It probably is a good idea, I was trying to start off as simple as possible.
What do you think of this signal Mag? It appears "on time" is about 80% of off time, and there is about 2.5-3 peaks during 1mS at 4000rpm, so approximately 1 high every 125 micro seconds at 10000rpm, or about 1 peak every 8000 clk cycles, maybe 40% of that is "on time" so say worst case scenario about 3000 clk cycles at 10000rpm. Do you still feel there is not enough time?
@Sapieha
Here is the comparison on each side of the 466Ω resistor (channel 2 is before, channel 1 is after)
3.3 zener
5.1 zener
And here are two samples on the scope with the schmitt trigger as part of the circuit, one at idle (900rpm) and one at 4000rpm. Channel 2 is the signal going into the schmitt, channel 1 is the signal coming out of the schmitt.
What do you think of the wave shape and can you tell me what you mean by "logic-levels" and then I will record them with the scope?
Below is what the signals look like with the schmitt in place, and they are compatible with the prop. Just to test, I was able to get the old spin code to read the values, but it would choke when I revved the engine up and freeze? The old spin code reads the time from one peak to the next, I'm trying to figure out how the PASM code is different, at idle there is something like 166000 clk cycles in between each logic high, even if 1/10th of that were the time waitpeq had to work, that's still 16k cycles, which I would think would be plenty of time for PASM? To me it looks like the logic low is about 3 times as long as the logic high. I don't know if I can tweak the PASM code on my own, but before I attempt that, do you agree?
So, the code above implies, that the low can be shorter and that the high has to be at least long enough to do all the calculations. And here the worst case scenario counts, so all conditions are in a state that the longest possible branch is taken.
I guess what you currently have in the real setup is, that the low-phase is long and you only have high peaks which are to short.
Can you also record logic-levels with the PropScope? It would be interesting to see those with a good time-resolution, because that's what's used in the end.
Possible solution: Add an inverter to the hardware?
It's actually partially working, the rpm will sometimes be correct, it appears to me it is not recognizing the gaps based on the PST output?
Here is how the long gap looks on the scope at high resolution, it's about 5.5mS at 1000rpm for the long gap
Here is how the standard gaps look at high resolution, it's about 1.5mS at 1000rpm for the standard gaps
I don't know if you're still interested in this, but the hardware circuit I built is giving a square wave signal now (or really close) and I can read it at low rpm's with spin, with my old spin code.
I'd love to be able to read it with PASM so that I don't have to be concerned with speed. Below is the code I used to read the rpm in spin, I'm not sure I can tweak the PASM code on my own, so I'd appreciate your help if you are still interested.
PUB calculateCrankRpm(localCrankPeriod) | elapsed
elapsed := -cnt
l_previousNum8 := l_previousNum7
l_previousNum7 := l_previousNum6
l_previousNum6 := l_previousNum5
l_previousNum5 := l_previousNum4
l_previousNum4 := l_previousNum3
l_previousNum3 := l_previousNum2
l_previousNum2 := l_current
l_current := localCrankPeriod
if (l_timeStampCrank == 0)
l_timeStampCrank := (cnt + clkfreq)
if (l_previousNum8 <> 0 and l_previousNum7 <> 0 and l_previousNum6 <> 0 and l_previousNum5 <> 0 and l_previousNum4 <> 0 and l_previousNum3 <> 0 and l_previousNum2 <> 0)
l_previous7average := ((l_previousNum8 + l_previousNum7 + l_previousNum6 + l_previousNum5 + l_previousNum4 + l_previousNum3 + l_previousNum2)/7)
l_tCnt += 1
if (((l_current/10)*100) > ((l_previous7average/10)* 110)) or (((l_current/10)*100) < ((l_previous7average/10)*90))
l_Cnt += 1
if (cnt > l_timeStampCrank)
l_timeStampCrank := (cnt + clkfreq)
l_CntPeak := l_Cnt ' I should a number here matching see rpm/60, for a frequency of 6000 I should see 166.67 of these each second
l_CntPeakRead := false
l_Cnt := 0
l_current := l_previous7average
else
if (((clkfreq/l_current)*60)/36) < 200
pst.str(string("crap2"))
pst.char(13)
else
l_toothRpm := (((clkfreq/l_previous7average)*60)/36)
elapsed += cnt - 544
Far away
Up close showing the gap and the first tooth (I'm not sure why it elongates the first tooth)
Up close to compare the logic highs and logic lows
And how it looks on the pst (next I'll be cleaning this up so it displays like you set it up)
Here is a close up of the crank trigger wheel and the bridge/gap
I've been off for a while - just watching with my mobile, but it's no fun to write!
Looking at the screenshots I think the problem is that the GAP detection is not working properly. I think I already mentioned somewhere that it needs to be changed?
Anyways, one thing I'd do is add an inverter after your schmitt trigger because on some screenshots it looks like the high-time after the gap is not really longer then the usual tooth-high-time. The low time seems to be more reliable.
Or you can maybe change the code by exchanging both waitpxxx-instructions. Need to think about it. Or you simply try it ;o)
Why exactly do you start with this previousNum thing again? You remember that we already calculate an average using the moving-average code?
When I was having trouble reading the frequency with PASM, I wanted to see how it did with spin just as a baseline. The code I posted works at lower rpm, but does not have the speed needed for higher rpm. It was just me verifying that the hardware portion was working.
You are right, gap detection is not working, the rest of the calculations seem to be working though since the new hardware was implemented and I was able to copy and imitate the signal perfectly with the prop.
At least I can now test confidently, knowing that the signal I generate with the prop matches the signal the cars hardware is putting out perfectly.
Here is the code I use to generate the proper signal
crank.spin
CON
' current code only allows from 1Hz upwards
MIN_FREQUENCY = 1
' dunno which upper limit can be achieved by the SPIN-code
MAX_FREQUENCY = 9200
VAR
byte pin, presentTeeth, missingTeeth, cog
long frequencyRate
long pulse, sync, timing, l_localIndex
long stack[100]
PUB start(p, pt, mt, initFrequency)
pin := p
presentTeeth := pt
missingTeeth := mt
setFrequency( initFrequency )
if cog
cogstop( cog-1 )
'cog := cognew( crankPulser, @stack )+1
cog := cognew(simulateCrankWheel(pin, presentTeeth, missingTeeth, initFrequency), @stack )+1 'simulateCrankWheel(4, 36, 2, 15000)
PUB setFrequency( newFrequ )
frequencyRate := (newFrequ #> MIN_FREQUENCY ) <# MAX_FREQUENCY
timing := clkfreq / frequencyRate
pulse := timing >> 1
PUB getFrequency
return frequencyRate
PRI crankPulser
dira[pin] := 1 ' make output
frqa := 1
phsa := 0
ctra := (%00100 << 26) | pin ' setup counter for pin
sync := cnt ' sync with system clock
repeat
repeat l_localIndex from 1 to (presentTeeth- missingTeeth)
phsa := -pulse ' create 1ms pulse
waitcnt(sync += timing) ' hold for current hz setting
repeat l_localIndex from 1 to (missingTeeth)
phsa := 0 ' create 1ms pulse
waitcnt(sync += timing)
PUB simulateCamWheel(simCamWheelPinNum, engineRPM, crankRotsToCamRots, degreeOfTrigger1, degreeOfTrigger2, degreeOfTrigger3, camTriggerDegreeWidth) | localIndex1, crankRpmPerSecond, camRpmPerSecond, localCnt, trigger1Wait, trigger2Wait, trigger3Wait, emtpySpace1Wait, emtpySpace2Wait, emtpySpace3Wait, cyclesPerDegOfRot
dira[simCamWheelPinNum]~~
outa[simCamWheelPinNum]~
localIndex1 := 0
crankRpmPerSecond := (engineRPM/60) ' 166.667 crank rots per second at 10k rpm
camRpmPerSecond := (((engineRPM*10)/60)/2) ' 83.333 cam rots per second at 10k rpm
cyclesPerDegOfRot := (((clkfreq/camRpmPerSecond)/360)*10) ' 960000/360 at 10k or 2660
trigger1Wait := (camTriggerDegreeWidth*cyclesPerDegOfRot) ' 13300 / 5 degrees
trigger2Wait := (camTriggerDegreeWidth*cyclesPerDegOfRot) ' 13300 / 5 degrees
trigger3Wait := (camTriggerDegreeWidth*cyclesPerDegOfRot) ' 13300 / 5 degrees
emtpySpace1Wait := (degreeOfTrigger1*cyclesPerDegOfRot) ' 266000 / 100 degrees
emtpySpace2Wait := (degreeOfTrigger2*cyclesPerDegOfRot) ' 266000 / 100 degrees
emtpySpace3Wait := (degreeOfTrigger3*cyclesPerDegOfRot) ' 385700 / 145 degrees
' + = 957600 (((clkfreq/957600) * 60sec/min) * 2rots campercrank) = 10krpm
localCnt := cnt
repeat
outa[simCamWheelPinNum]~~
waitcnt(localCnt += trigger1Wait) ' 6665
outa[simCamWheelPinNum]~
'waitcnt(localCnt += (clkfreq/521)) ' 160000-6665
waitcnt(localCnt += emtpySpace1Wait)
outa[simCamWheelPinNum]~~
waitcnt(localCnt += trigger2Wait) ' 6665
outa[simCamWheelPinNum]~
'waitcnt(localCnt += (clkfreq/521)) ' 160000-6665
waitcnt(localCnt += emtpySpace2Wait)
outa[simCamWheelPinNum]~~
waitcnt(localCnt += trigger3Wait) ' 6665
outa[simCamWheelPinNum]~
'waitcnt(localCnt += (clkfreq/521)) ' 160000-6665
waitcnt(localCnt += emtpySpace3Wait)
PUB simulateCrankWheel(simCrankWheelPinNum, crankTeethSimToothCnt, crankTeethSimMissingToothCnt, crankTeethSimRPM) | localIndex, waitTime, logicHighWaitTime, logicLowWaitTime, logicHighGapWaitTime, logicLowGapWaitTime, startCnt, existingTeeth
' Initialize variables here
dira[simCrankWheelPinNum]~~
outa[simCrankWheelPinNum]~
localIndex := 0
waitTime := (clkfreq/((crankTeethSimRPM*(crankTeethSimToothCnt*2))/60)) ' for 1500rpm = 44444
logicHighWaitTime := ((waitTime/50)*45)
logicLowWaitTime := ((waitTime/50)*55)
logicHighGapWaitTime := ((((waitTime*6)/100)*43)-logicHighWaitTime) ' this percentage can be measured by a prop scope and a signal capture
logicLowGapWaitTime := ((((waitTime*6)/100)*57)-logicLowWaitTime)
existingTeeth := (crankTeethSimToothCnt - crankTeethSimMissingToothCnt)
startCnt := cnt
{ctra := (%00100 << 26) | simCrankWheelPinNum ' setup counter for pin
frqa := 1 ' this makes the cnt screw up every 26 seconds
phsa := 0}
{
This is for a Hall effect signal/sensor simulation
For 1000rpms on a 36-2 crank trigger wheel, it'd be (1000(36*2)) = 72000, then 72000/60 = 1200 teeth on/teeth off per clkfreq. 80000000(clkfreq)/1200 =
a tooth on or tooth off every 133333.3 clk cycles. It would be on for 66666.67 clk cycles and then was off for every 66666.67 clk cycles
So you'd loop on for 66666.67 cycles and then off for 66666.67 cycles and after 34 times, you'd wait for 66666.67 cycles * 4, to simulate the 2
missing teeth on and accompanying 2 "missing" teeth off (or gaps in between each tooth) . It should account for 2 missing teeth and 3 missing toothgaps
An example to start the method would be simulateCamWheel(7, 3, 105, 100)
}
repeat
repeat localIndex from 1 to (existingTeeth) ' loop number of physical teeth on and off
outa[simCrankWheelPinNum]~~
'l_crankTeethSimOn := startCnt
waitcnt(startCnt += logicHighWaitTime) ' on for 1 tooth
outa[simCrankWheelPinNum]~
'l_crankTeethSimOff := startCnt
waitcnt(startCnt += logicLowWaitTime) ' off for 1 tooth
repeat localIndex from 1 to 1'(crankTeethSimMissingToothCnt) ' loop number of times for each missing tooth
outa[simCrankWheelPinNum]~
'l_crankTeethSimOff := startCnt
waitcnt(startCnt += logicLowGapWaitTime) ' off for 57% of 2 teeth
outa[simCrankWheelPinNum]~~
'l_crankTeethSimOff := startCnt
waitcnt(startCnt += logicHighGapWaitTime) ' on for 43% of 2 teeth
I have some good news, I haven't checked this on the car, only the sim method, but I noticed gap cnt was 2x what it should be, I believe because the a/c gap wave, which is about the width of 4 teeth is being split at the digital conversion so that the digital version (ac wave split in half) has a longer than usual low, but also a longer than usual high, both being about the equivalent width of 2 teeth. Unfortunately it does not work at lower rpm
I think this is causing the gap portion to count twice? So when I call the method, I've just done a /2 now.
The question is, how do I go from here?
I don't exactly know how I want to go about this, but I know my end goal. I want to be able to compare the cam sensor location to the crank sensor location and then adjust the cam sensor location via a simple transistor, until the feedback from the cam sensor location matches where I'd like it to be. Maybe I can do a time comparison based on trigger tooth 1 of both the cam sensor trigger and the crank sensor trigger and then calculate the offset? Does that make sense?
The code I use to simulate the cam sensor output, which I wrote this weekend and it simulates the cam sensor output perfectly.
Do you have any guidance on how to reference trigger tooth 1 of each sensor and then offset it the amount of degrees or time accordingly?
Comments
These files I tried to name appropriately also
idle is again approximately 900rpm and I can get different rpm and time division readings if you would like to see those.
When I come home tonight I am going to try and write some code reflecting the things you've said in your last post.
But what I don't understand ... why don't you try to get the most information out of it? If you compare the 5000/1ms crank reading with the 5000/20ms reading, the first one is much better, right? And with a bigger sample rate it would be even better than that. But OK, most interesting so far is that it's a symetrical alternating voltage somewhere close to 5V @ 700RPM and 10V @ 5000RPM.
Just did some calculations and it looks like the idle RPM is more in 700 range.
The prop scope has 2 channels, right? Why didn't you measure the crank and the cam at once? You'd then see how both signals are related. ;o)
Did you already tell what all this efford is good for? What's your goal? Just out of curiosity .. and if you are willing/allowed to share this information.
With your question, do you mean why did I not go to the point where a single sine wave encompassed the entire prop scope screen? I did a couple of different samples because I wanted to show the bridge/gap in at least one of them since this signal is different than your average signal, in that it physically disappears after 34 cycles for 2 of the cycles. I can do a few different samples or overlay them with the second channel if you'd like, just let me know the scenarios and I'll grab them. I should have overlayed them, I just didn't think about it . 700rpm may be correct, the factory tachometer isn't known for its accuracy and I was looking at it from the passenger seats angle as well.
My ultimate goal is to be able to adjust the cam angle in relation to the crank angle, which the factory ecu currently does right now. It has an oil pressure controlled gear, of which the oil pressure is modulated by a solenoid. It can change the cam angle up to 30 degrees before or after the "0 angle" location that it would be stationary at, if the auto manufacturer had decided to not make it adjustable.
If I can get that far, there are then a few other things I would like to do in addition to that.
I have gotten the bulk write to partially work, and I'm not 100% sure why. Some of the values are working and some are not, maybe you can explain what I didn't do correctly (code is attached)?
I'm still struggling with the bulk write, I drew out a chart with cells on how cog ram maps to hub ram, to try and help myself visualize this. By the way, can I tell PASM to write to the hex address itself and not a pointer to an address?
So the longs in hub ram map to par, par + 1, par + 2, par + 3 etc, (assuming it has been coded that way) . If par was @1B, then cog ram would map to $1B - $1E? and par + 1 would map to $1F - $22, par + 2 would map to $23 - $26 etc ... is this correct? Do the numerical values at the bottom, where the variables for PASM are instantiated, represent an allocation number of bytes? Is that why they are ordered as 4, 8, 12, 16, 20, 24, 28, 32? I think this will be easier for me to understand, if I can picture in my head how the values are mapped and how those numerical columns affect things.
If so, I'm not sure why my code is not working properly, basically it is supposed to follow the logic of
cog ram long, write to par
cog ram long + 1, write to par + 4
cog ram long + 2, write to par + 8
Is this logic at least correct?
Here is the variable value definition section:
The method I'm trying to get working:
The variable name assignment:
par inside of a COG is like a readonly variable which contains the content of the second parameter of the COGNEW:
cognew( @mypasm, @first_var )
is like
par := @first_var & %11111111_11111111_11111111_11111100
This is why you'll never find a $1b here. Remember, the address has to be long alligned!
par is a pointer to HUB-RAM in our case pointing to the first variable. And even if this address has to be long alligned, it's still a HUB-RAM address which is a byte-address. So, if the first variable is a long you get to the address of the next variable by adding 4 (par + 4). The next long after that has address par + 8 ... and so on.
If you'd then have a number of words in the variable list, you'd only add 2 from one to the next word.
4, 8, 12, 16, 20, 24, 28, 32 are simply the offsets related to tmp1, which has the same value as par. The initialization at the end plus all the "add x, tmp1" instructions work like this:
cyclepntr := par + cyclepntr which is par + 4
toothcntptr := par + toothcntptr -> par + 8
...
The logic of the loop is correct!
The problems you have are:
1. wrlong cycles+varLongCount, cyclepntr ' * write cycles to cyclepntr
does not work as you expect it to work AND
2. you did not understand how to go forward to the next COG-RAM location in the wrlong
About 1.:
cycle is a label which is converted to a COG-RAM address by the compiler. varLongCount is a label which is converted to a COG-RAM address as well. cycle + varLongCount simply adds those addresses. What you expected was use the cycle address an add the content of varLongCount.
About 2.:
In PASM you have to use self-modifying code to forward addresses used by an instruction. If you have a look at an instruction on bit-level things get clearer:
iiiiii_czri_cccc_ddddddddd_sssssssss
i is the instruction opcode iteslf, so you find different values here for mov, sub, add, wrlong .....
czri are the flags that tell the COG whether the carry flag, the zero flag shall be written (WC, WZ), whether the instruction shall be readonly (WR, NR) and whether the source bits are direct addresses ( # ).
cccc are the conditional execution flags ( IF_Z, IF_NZ, IF_B ..... )
ddddddddd is the destination address - for example cycles -address would be placed here.
sssssssss is the source address
So, if you want to increment the destination-address you actually have to add
000000_0000_0000_000000001_000000000
to the instruction itself. That's why I gave you the code
add wrlongLoop, add1toDest
It should have been fully functional - no need to add the varLongCount stuff ;o) The only thing missing is a jump back to a meaningfull loop after the cleanup-code and before the definition of resetWrlong.
The cleanup restores the original instruction of wrlongLoop, so that dest points to cycles again and resets the cyclepntr.
Hope that helped!
I think I have noticed something I didn't realize before, cogram can technically only store 9 bits of data if it were a single long? And the remaining 23 bits in the register are used for other various functions? If this is correct, why is this?
I understand the value of having the hubram values in the proper order, and the corresponding cogram values also being in order but I'm still not able to make this code work completely. I'm not sure what I'm doing wrong but I'm going to try and test with it in the car rather than spend more time trying to figure out why I can't get the bulk write loop to work. Based on what you've told me, my loop is correct now and my order of longs is correct now, so it is something else that is hosing delta t, peak cnt and min. They all work when I have the separate writes though.
I just noticed you can compile and look at the memory addresses in the prop IDE, strangely enough averagex does not have a hex address? Maybe this is my problem? I also thought it was strange that min was set to -1?
Because you want to have all informations needed to execute an instruction to fit into 32 bit. So, if you want to use direct data the only place to store it is the source address and setting the I-flag (immediate)accordingly. If you'd allow immediates with any size lager than 9 bit you'd have to occupy the next long anyway, so in terms of COG-RAM usage there is really no difference in using registered data vs. having longer immediate data. The difference is in the details of the processor internal circuitry needed. If you want to support different size immediate you need flexible instruction lenght which needs more complex logic. In fact this is what CISC processors really have - a lot of addressing modes and flexible instruction sizes...
"...rather than spend more time trying to figure out why I can't get the bulk write loop to work."
Fine, as I said bulk write is an optimization in terms of COG-RAM usage and not really needed right now as there is still some COG-RAM left for adding more code. I'll take care of it the next days.
"... averagex does not have a hex address?"
averagex and avgcnt use the same address - one memory location, different labels, where each label matches the usage of the memory location. If you have a closer look at the usage of the code, you'll see that in the beginning avgcnt is used and once we have more than 7 samples, the averagex is used. I simply implemented the moving average in a way that it only spits out an average if you have the right number of samples (=8 in this case) in the database. Otherwise you'd need a counter storing the actual number of samples and even more important, you'd really need a division for calculating the right average for the given number of samples. Fix it to 8 you don't need a counter and you can use >>3 to divide the database by 8.
Additional explaination:
mov calcavg, nopinstr ' * move calcavg to nopinstr
This actually replaces the "djnz avgcnt..." with a NOP - an operation which is doing nothing. From there on the averagex-code becomes effective for all following loops and avgcnt is no longer used.
"I also thought it was strange that min was set to -1?"
1 = $0000_0001
-1 = - ($0000_0001) = $FFFF_FFFE + 1 = $FFFF_FFFF which is the biggest number you can store in a long when using unsigned compare
( $FFFF_FFFE + 1) is the two's complement of 1 -> http://en.wikipedia.org/wiki/Two%27s_complement
@kuroneko:
Thanks for that ... nice to have you as a backup!
So each cog has 512 long registers (I know a few of them are special purpose)
So each cog can work with a maximum of ~500 individual 9 bit values (possibly x 2 with some creative coding) ... if that is true, now I see why you are teaching me early on about register conservation
And somewhere around 9000 bits are available for data source and destination usage within ~500 registers per cog.
I've heard risc/cisc but I did not have a general understanding of the terms until now after your comments prompted me to find this. http://www-cs-faculty.stanford.edu/~eroberts/courses/soco/projects/2000-01/risc/risccisc/
I learned 2's compliment a few years ago, I'm going to have to refresh my memory of that with the wiki link.
Honestly, I think being explained the architecture of the chip really helps you understand PASM and why it does things the way that it does, this has been very beneficial.
FIT 496
statement at the end. This tells the compiler that the code between ORG 0 and FIT should not extend 496 longs. Otherwise the compiler complains.
Each COG has 496 32bit registers for free usage. It does not care whether you put instructions or data there. If you use instructions you have 9 bits for a destination address and 9 bits for a source address xor a 9 bit immediate value. It's also 9 bit by purpose: the max. value a 9 bit number can store is ???? 511! Starting at 0 this makes 512 different values and make sure that each instruction can address each other register in COG RAM.
If you have stored some data in COG RAM it's your responsibility what the content means. If you want you can pack 3 x 9bit-values in one long. Or 16 x 2 bit values. It's your code which then has to deal with this kind of data.
So, if you want to count how many 9 bit values fit into a COG you have to take this into account - even if I don't really understand what this counting is good for ;o)
The prop was reading but the calculations were off and I'm trying to figure what I did wrong in my bench preparation/testing? I really thought my bench test scenarios were solid. The following screen capture is what the condition signal looks like on the prop scope and what the pst was reading in relation to that signal at that moment. I'm working on getting the voltage peaks a little closer to 3.3v, but I'm not sure that is affecting my reading of the pulses.
Any thoughts?
a) Crank.spin creates a symetrical signal, 50% pulse and 50% pause
b) the CrankSensor works asymetrical
So, the code above implies, that the low can be shorter and that the high has to be at least long enough to do all the calculations. And here the worst case scenario counts, so all conditions are in a state that the longest possible branch is taken.
I guess what you currently have in the real setup is, that the low-phase is long and you only have high peaks which are to short.
Can you also record logic-levels with the PropScope? It would be interesting to see those with a good time-resolution, because that's what's used in the end.
Possible solution: Add an inverter to the hardware?
I did snap another screen shot before going to work this morning, as I remember you had told me previously that you like also seeing the shape of the wave. To me the waves look pretty close, what do you think, do you still feel there isn't enough time during the peaks? I can definitely try and inverter.
With the 3.3v zener
With the 5.1v zener (for comparison)
Why not use Smith trigger to condition them from analog to Digital signals.
I had considered that in the final design portion of the circuit to give myself some hysteresis, but at this development point I was just hoping to test the code out. I may have to go to a schmitt trigger sooner than I anticipated though. It probably is a good idea, I was trying to start off as simple as possible.
My concern are that if You start simple without --- You will work 2 times on same programing
As none of Yours previous timings will be same
I have a few of these at home somewhere, maybe it is time I used one.
http://www.nxp.com/documents/data_sheet/74HC7014_CNV.pdf
Good choice ---- But Ground all unused inputs.
@Sapieha
Here is the comparison on each side of the 466Ω resistor (channel 2 is before, channel 1 is after)
3.3 zener
5.1 zener
And here are two samples on the scope with the schmitt trigger as part of the circuit, one at idle (900rpm) and one at 4000rpm. Channel 2 is the signal going into the schmitt, channel 1 is the signal coming out of the schmitt.
idle
4000rpm
Below is what the signals look like with the schmitt in place, and they are compatible with the prop. Just to test, I was able to get the old spin code to read the values, but it would choke when I revved the engine up and freeze? The old spin code reads the time from one peak to the next, I'm trying to figure out how the PASM code is different, at idle there is something like 166000 clk cycles in between each logic high, even if 1/10th of that were the time waitpeq had to work, that's still 16k cycles, which I would think would be plenty of time for PASM? To me it looks like the logic low is about 3 times as long as the logic high. I don't know if I can tweak the PASM code on my own, but before I attempt that, do you agree?
It's actually partially working, the rpm will sometimes be correct, it appears to me it is not recognizing the gaps based on the PST output?
Here is how the long gap looks on the scope at high resolution, it's about 5.5mS at 1000rpm for the long gap
Here is how the standard gaps look at high resolution, it's about 1.5mS at 1000rpm for the standard gaps
I don't know if you're still interested in this, but the hardware circuit I built is giving a square wave signal now (or really close) and I can read it at low rpm's with spin, with my old spin code.
I'd love to be able to read it with PASM so that I don't have to be concerned with speed. Below is the code I used to read the rpm in spin, I'm not sure I can tweak the PASM code on my own, so I'd appreciate your help if you are still interested.
Far away
Up close showing the gap and the first tooth (I'm not sure why it elongates the first tooth)
Up close to compare the logic highs and logic lows
And how it looks on the pst (next I'll be cleaning this up so it displays like you set it up)
Here is a close up of the crank trigger wheel and the bridge/gap
I've been off for a while - just watching with my mobile, but it's no fun to write!
Looking at the screenshots I think the problem is that the GAP detection is not working properly. I think I already mentioned somewhere that it needs to be changed?
Anyways, one thing I'd do is add an inverter after your schmitt trigger because on some screenshots it looks like the high-time after the gap is not really longer then the usual tooth-high-time. The low time seems to be more reliable.
Or you can maybe change the code by exchanging both waitpxxx-instructions. Need to think about it. Or you simply try it ;o)
Why exactly do you start with this previousNum thing again? You remember that we already calculate an average using the moving-average code?
When I was having trouble reading the frequency with PASM, I wanted to see how it did with spin just as a baseline. The code I posted works at lower rpm, but does not have the speed needed for higher rpm. It was just me verifying that the hardware portion was working.
You are right, gap detection is not working, the rest of the calculations seem to be working though since the new hardware was implemented and I was able to copy and imitate the signal perfectly with the prop.
At least I can now test confidently, knowing that the signal I generate with the prop matches the signal the cars hardware is putting out perfectly.
Here is the code I use to generate the proper signal
crank.spin
stepbystep.spin
I have some good news, I haven't checked this on the car, only the sim method, but I noticed gap cnt was 2x what it should be, I believe because the a/c gap wave, which is about the width of 4 teeth is being split at the digital conversion so that the digital version (ac wave split in half) has a longer than usual low, but also a longer than usual high, both being about the equivalent width of 2 teeth. Unfortunately it does not work at lower rpm
I think this is causing the gap portion to count twice? So when I call the method, I've just done a /2 now.
The question is, how do I go from here?
I don't exactly know how I want to go about this, but I know my end goal. I want to be able to compare the cam sensor location to the crank sensor location and then adjust the cam sensor location via a simple transistor, until the feedback from the cam sensor location matches where I'd like it to be. Maybe I can do a time comparison based on trigger tooth 1 of both the cam sensor trigger and the crank sensor trigger and then calculate the offset? Does that make sense?
The code I use to simulate the cam sensor output, which I wrote this weekend and it simulates the cam sensor output perfectly.
Do you have any guidance on how to reference trigger tooth 1 of each sensor and then offset it the amount of degrees or time accordingly?