shift out with CTR
A while back I asked how it can be done, but I never got a straight answer.
I've looked at other peoples' code that have done it, but it is always somewhat hard to decipher CTRs (at least for me...I've never been good with them).
I am trying to shift out a 5-bit value. It should use both CTRs (one for the serial data, one for the clock), I am assuming.
Preferably, it would run at a specific speed (regardless of current clock speed -- but it has to be outputting at a max of about 2MHz).
Any help would be nice. About all I know is that the first CTR would be NCO mode, but controlling the speed and setting up the second CTR so it would output clock pulses between the first CTR's data changes....it escapes me.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
I've looked at other peoples' code that have done it, but it is always somewhat hard to decipher CTRs (at least for me...I've never been good with them).
I am trying to shift out a 5-bit value. It should use both CTRs (one for the serial data, one for the clock), I am assuming.
Preferably, it would run at a specific speed (regardless of current clock speed -- but it has to be outputting at a max of about 2MHz).
Any help would be nice. About all I know is that the first CTR would be NCO mode, but controlling the speed and setting up the second CTR so it would output clock pulses between the first CTR's data changes....it escapes me.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!

Comments
For me to understand counters, I had to think about the relationship of FRQx ("Frequency Register") and PHSx ("Phase Register") (I'll just use A for example). The entire job of counter A is to add FRQA to PHSA at certain times, and optionally control 0 or 1 or 2 output pins. Table 6 in the propeller datasheet shows when FRQA will be added to PHSA. In the NCO counter mode, the counter will ALWAYS add FRQA to PHSA (this means at each clock tick, so if you are using a 5MHz crystal and PLL16X, then the counter is doing the accumulation at 80MHz). But the second half of its job is to set the A pin (which you specify when setting the CTRA ("Control Register") register in bits 5..0) to whatever bit 31 is of PHSA.
Now, the trick to shifting data out using the NCO counter mode is to set FRQA to 0, so PHSA never actually gets updated by the counter. However, the counter is still busy doing its job of making sure the A Pin is set to the high bit of the PHSA register. So, to shift out the value we want, we can just load PHSA up with our value, then ROR or ROL the PHSA register a bunch of times, and each time we move a new bit into the PHSA[noparse][[/noparse]31], the counter mirrors that on pin A.
You need to make sure you keep your data line and your clock line in sync. I use both counters where one controls the CLK line and one controls the DataOut line to shift out one bit per instruction (using Propeller Assembly...Spin is too slow and non-deterministic for this to work). However, when you do it this way, you do not get to set the actual clock-out speed...it is a function of the clock speed of the propeller. So running at 80MHz means that the data will be clocked out at 20MHz. If you use PLL8X, then suddenly the data rate is now 10MHz. There isn't an easy way to throttle back the speed using the 2 counters technique.
So you will probably want to use a combination of setting the clock line high and low using a single PASM command each, shift out the data using the NCO counter mode trick, then use a waitcnt to throttle back the speed. If you are running at 80MHz and you want to hit 2MHz clock output rate, then you get to use 10 instructions per bit out (20 MIPS / 2 MHz). You could easily do something like the following (untested) code:
:loop or outa,maskCLK andn outa,maskCLK waitcnt timestamp,deltatime ror phsa,#1 djnz bits_left,#:loopNote that this assumes you want to shift the data out LSB first, and that you set up the counter and phsa and the timing values ahead of time. There is most likely a way to use the video generator, but I have not looked into it at all.
hth,
Jonathan
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
lonesock
Piranha are people too.
Post Edited (lonesock) : 7/24/2009 7:42:12 PM GMT
You can't use the PLL divider on NCO (or am I not understanding how that works as well).
Here is the code I am using now:
'' input value is shifted all the way left so MSB of output is in bit[noparse][[/noparse]31] of val shift_out SHL val, #1 WC ' shift output value and place bit 31 in C MUXC OUTA, DPin ' set data pin to what value bit 31 was OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle DJNZ Bits, #shift_out ' cycle through the rest of the valueSame number of instructions as your code (not using the CTRs).
I just want a little faster. Maybe output at full speed as long as the clock pin is being controlled by the CTR.
I realize now though that this code is 4MHz (at 80MHz clock). That's twice as fast as the datasheet says the MCP3208 should be able to handle...yet not a single problem; but hey, I always want to go faster! If it is running this fast at 3.3V, it should be able to go about 80% faster at 5V.
Ultimately I could all but ignore CTRs if the propeller did a (nowadays, normal) single-cycle execution of it's ASM code.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
By using a counter as a shift register, you eliminate the need for the MUXC. That saves one instruction. You can also unroll your loop to save the DJNZ. Lonesock's code was as long as yours, because he paced it with a waitcnt, which you did not do.
-Phil
To clarify, I would do something like this:
MOV CTRA, nco MOV PHSA, val shift_out SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle DJNZ Bits, #shift_out ' cycle through the rest of the value nco LONG %00100 << 26 val LONG %11000 << 26 ' output value Bits LONG 5or
MOV CTRA, nco MOV PHSA, val shift_out SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle nco LONG %00100 << 26 val LONG %11000 << 26 ' output value▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
entry MOV CTRA, nco ADD CTRA, DPin2 MOV PHSA, val OR DIRA, DPin OR DIRA, CPin shift_out SHL PHSA, #1 ' shift output value OR OUTA, CPin ' start clock cycle ANDN OUTA, CPin ' end clock cycle DJNZ Bits, #shift_out ' cycle through the rest of the value COGID val COGSTOP val nco LONG %00100 << 26 val LONG %11000 << 26 ' output value Bits LONG 5 DPin LONG |< 19 DPin2 LONG 19 CPin LONG |< 20▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
I see you've gone back to the loop form, so apparently you don't need it to be any faster than it is. If you had, though, there's a way to send the clock pulse in one instruction, rather than two.
I'm a little bit curious, though: why, if you're starting and stopping an entire cog just to send five bits, do you need the transfer itself to be so fast?
-Phil
and the second counter to send out the clock waveform at 2MHz (for e.g.).
Make sure your 1st data bit is ready to go in PHSA[noparse][[/noparse]31], then set the clock
line high and start your CLK counter. Then in your loop:
:loop waitpne maskCLK,maskCLK rol phsa,#1 djnz bits_left,#:loopIf I did that right, the cog will hang until your clock line goes low, then will
move out the next data bit.
Jonathan
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
lonesock
Piranha are people too.
[b]CON[/b] [b]_clkmode[/b] = [b]xtal1[/b] + [b]pll16x[/b] [b]_xinfreq[/b] = 5_000_000 [b]PUB[/b] Start [b]cognew[/b](@fast_ser, 0) [b]DAT[/b] [b]org[/b] 0 fast_ser [b]mov[/b] [b]ctra[/b],[b]ctra[/b]0 'CTRA is NCO output w/ FRQA == 0. [b]mov[/b] [b]phsa[/b],data 'Data is in PHSA. [b]mov[/b] [b]ctrb[/b],[b]ctrb[/b]0 'CTRB is NCO output w/ FRQB == 1. [b]mov[/b] [b]frqb[/b],#1 [b]mov[/b] [b]dira[/b],#%11 'Set both pins to output. :loop [b]ror[/b] [b]phsa[/b],#1 'Rotate data. [b]neg[/b] [b]phsb[/b],#3 'Send a pulse 3 clocks long. [b]jmp[/b] #:loop [b]ctra[/b]0 [b]long[/b] %00100 << 26 | 1 'Data on A1. [b]ctrb[/b]0 [b]long[/b] %00100 << 26 | 0 'Clock on A0. data [b]long[/b] $aa55_aa55-Phil
That was just me testing ... seeing if I could get something remotely what I wanted to come out.
It is actually (now) already implemented in my MCP3XXX driver: obex.parallax.com/objects/488/ without loops...so ends up being 3 instructions per output bit.
@Phil: That also sounds fast, and there are things I could use on the outgoing and incoming data.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
Jonathan
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
lonesock
Piranha are people too.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!
Don't forget to shut CTRB down when you're done sending data, or set FRQB to 0. Otherwise, after about 25 seconds, it will send a positive-going edge on its own.
-Phil