Shop OBEX P1 Docs P2 Docs Learn Events
First attempt at assembly — Parallax Forums

First attempt at assembly

AJMAJM Posts: 171
edited 2009-10-02 17:40 in Propeller 1
I’m trying to introduce myself to pasm. While I haven’t mastered spin as of yet, I feel I really need to understand at least some assembly to understand most of the code floating around this place.

I need to supply an analog voltage (0 ~ 3.3V will be fine) for a project I’m attempting. Since there is an example of this in the application notes, I thought I would start there.

Below is the file I am referencing:

{{ Demonstration of scaling Duty Cycle
                     10kΩ
                APIN ─┳── Out
                        │
                       .1µF
                        
Delta modulation has no fundamental freq but has quantization noise  
}}
CON _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000

VAR long parameter

PUB go | x
  cognew(@entry, @parameter)
  repeat
    repeat x from 0 to period
      parameter := $20C49B * x '$1_0000_0000 / period
      waitcnt(1000 +cnt)

DAT
        org

entry   mov dira, diraval
        mov ctra, ctraval

        mov time, cnt
        add time, period

:loop   rdlong value, par
        waitcnt time, period
        mov  frqa, value                        
        jmp #:loop

diraval long |< 1 + |< 0
ctraval long    %00111 << 26 + 1<<9 + 0         'NCO/PWM APIN=0 BPIN=1
period  long 2000                               '800kHz period (_clkfreq / period)                      
time    res 1
value   res 1




Assuming that we have a 5Mhz crystal and pll = 16. Let me know if I understand this correctly.

The first cog, using loops, sets the variable ‘parameter’ to 2000 different values. Values are between 0 ~ 2^32 roughly. This occurs every 12.5us.

Meanwhile the second cog, which was started by the cognew(@entry, @parameter) command, has set up the counters, moved the value in cnt to time, added time to period and stored that value into time.

Next, I assume that the loop is started automatically in a linear fashion. The address of parameter is passed through par and its current value is stored, in this case, into the variable value.

The loop waits for 25us and then updates frqa. Jmp #:loop just repeats the loop. This effectively gives an analog output from 0 ~ 3.3V in a sawtooth wave.

If this is correct I have a few questions. I must be missing something however. I don’t understand how the cog that is updating the counters is accurate. Isn’t it missing every other value of parameter because it’s waiting twice as long?

More questions to come later. Thanks for the help.

Comments

  • ericballericball Posts: 774
    edited 2009-10-02 15:13
    entry   mov dira, diraval             ' dira := diraval
            mov ctra, ctraval             ' ctra := ctraval
    
            mov time, cnt                 ' time := cnt + period
            add time, period
                                          ' repeat
    :loop   rdlong value, par             '   value := parameter
            waitcnt time, period          '   waitcnt( time ); time += period
            mov  frqa, value              '   frqa := value
            jmp #:loop
    
    

    So, yes,·you are·correct.· The SPIN code is updating parameter every 1000 clock cycles, while the PASM code is reading parameter every 2000 clock cycles.· I suspect the problem is "period" is being used for two different purposes.· There should be a "delay" variable which is used for both waitcnt delays.

    There's also a gotcha in the code even with that change.· Note that the SPIN code uses waitcnt( cnt + delay) while the PASM code uses waitcnt( time ); time += delay.· The SPIN code is waiting delay cycles from the start of waitcnt, while the PASM code is waiting delay cycles from the previous waitcnt.· So the SPIN loop will take longer than the PASM code.· I'd recommend changing the SPIN code to be more like the PASM code: initialize time := cnt outside of the repeats (you can reuse the variable since the PASM variable will be in COG RAM) then use waitcnt( time += delay ) inside the repeats.

    In fact, there's no real reason to have a waitcnt in the PASM code at all.· It can simply read parameter and update frqa in a tight loop.· All of the delay is done by the SPIN code.· (You could also argue there's no reason to have two separate routines, the functionality of the PASM code could be done in SPIN just as easily.· It also could be done in pure PASM, but then you'd need to code the multiply routine.)


    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Composite NTSC sprite driver: Forum
    NTSC & PAL driver templates: ObEx Forum
    OnePinTVText driver: ObEx Forum
  • AJMAJM Posts: 171
    edited 2009-10-02 15:54
    Wow Eric, I really appreciate you explaining that. When I am home later I will try your suggestions.

    I have another concern about the below code:

    :loop   rdlong value, par             '   value := parameter
            waitcnt time, period          '   waitcnt( time ); time += period
            mov  frqa, value              '   frqa := value
            jmp #:loop
    
    



    I really do not understand the rdlong instruction. Couldn't this be replaced with a mov? Or why couldn't the third line be changed to "mov frqa, par." I'm sure its necessary but I do not know why.

    Also, is there necessarily a reason to have the counter in differential mode? Couldn't this be done in single ended mode?

    Thanks again,
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-10-02 16:04
    PAR is just the address "@parameter". So MOV would just put the hub address into value. What you want is PASM to read the value of the address and put it into cog ram. The SPIN equivalent code for "rdlong value, par" is "value := long[noparse][[/noparse]par]". There is a separation of information when you get into PASM. COG RAM vs. HUB RAM. Cogs don't have instant access to hub values. They must be read and writ to with the hub instructions. Yes, they are slower than MOV, but they give the cog access to the 32KB of shared RAM, instead of just being limited to the 2KB of cog RAM.

    By the way, wouldn't you want to swap the position of the MOV and the WAITCNT? That way the FRQA value is updated immediately, before the wait.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!

    Some of my objects:
    MCP3X08/4 ADC Driver - Programmable Schmitt inputs, frequency reading, and more!
    Simple Propeller-based Database - Making life easier and more readable for all your EEPROM storage needs.
    String Manipulation Library - Don't make strings the bane of the Propeller, bend them to your will!

    Post Edited (Bobb Fwed) : 10/2/2009 4:19:06 PM GMT
  • AJMAJM Posts: 171
    edited 2009-10-02 16:57
    Thanks Bobb, that does make sense.

    I see your point about swapping the MOV and WAITCNT. However, as Eric mentioned, the waitcnt is probably irrelevant. This isn't my code though. I found this in the propeller application notes, AN001, found here.

    I want to be able to use the prop as an DAC in the end. I thought this would be a good time to at least familiarize myself with at least some pasm.
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-10-02 17:40
    I really like PASM. The high-level coding SPIN provides is (as far as I know) unprecedented among microcontrollers, but it just can't keep up with custom PASM programs for speed. If you understand SPIN fairly well, PASM isn't a big jump, it is just a completely different way of thinking.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!

    Some of my objects:
    MCP3X08/4 ADC Driver - Programmable Schmitt inputs, frequency reading, and more!
    Simple Propeller-based Database - Making life easier and more readable for all your EEPROM storage needs.
    String Manipulation Library - Don't make strings the bane of the Propeller, bend them to your will!
Sign In or Register to comment.