Shop OBEX P1 Docs P2 Docs Learn Events
Assembly - Main RAM access — Parallax Forums

Assembly - Main RAM access

I'm fairly new to this. I'm sure it's been answered somewhere, but I can't find it.

My ultimate goal is to sample audio input and store it in Main RAM so it can be later used to send to a speaker, saved to SDcard, etc.

I understand how to pass an address to the cog and write a single value back to it. However, what I'm needing is an array of hundreds or thousands of samples. How to I set up an an array in Spin that I can then send the address of to the assembly cog to do this sampling, and how to I address and write the sampled values back to main RAM?

Comments

  • kwinnkwinn Posts: 8,697
    sbergren wrote: »
    I'm fairly new to this. I'm sure it's been answered somewhere, but I can't find it.

    My ultimate goal is to sample audio input and store it in Main RAM so it can be later used to send to a speaker, saved to SDcard, etc.

    I understand how to pass an address to the cog and write a single value back to it. However, what I'm needing is an array of hundreds or thousands of samples. How to I set up an an array in Spin that I can then send the address of to the assembly cog to do this sampling, and how to I address and write the sampled values back to main RAM?

    The cogs only have 2KBytes (512 longs) each, of which only 16 longs are in use. Hub ram is 32KBytes so you could store up to 16,384 16 bit samples which is at most only a second or two of audio. To store any more samples would require external ram.
  • I'm going to be sampling extremely low frequencies, so I won't need a sampling rate of more than 100-200 Hz. So I'll have plenty of space. Can you answer my question addressing question? I THINK I may have it working, but I'm just storing bad data, so it doesn't look like it's working.
  • kwinnkwinn Posts: 8,697
    edited 2017-03-08 16:11
    sbergren wrote: »
    I'm going to be sampling extremely low frequencies, so I won't need a sampling rate of more than 100-200 Hz. So I'll have plenty of space. Can you answer my question addressing question? I THINK I may have it working, but I'm just storing bad data, so it doesn't look like it's working.

    You would have the address in one of the cog registers (lets call it "address") and add 1, 2, or 4 to the address depending on whether you are writing a byte, word, or long to the address.

    For example 8 bit values would be:
    WRBYTE Value, Address  ' write byte to hub ram
    ADD Address, #1          ' increment the address
    

    How many bits of resolution are you storing?
  • edited 2017-03-08 16:48
    sbergren wrote: »
    I'm fairly new to this. I'm sure it's been answered somewhere, but I can't find it.

    My ultimate goal is to sample audio input and store it in Main RAM so it can be later used to send to a speaker, saved to SDcard, etc.

    I understand how to pass an address to the cog and write a single value back to it. However, what I'm needing is an array of hundreds or thousands of samples. How to I set up an an array in Spin that I can then send the address of to the assembly cog to do this sampling, and how to I address and write the sampled values back to main RAM?

    Declare an array in hub ram and pass the address when you start the cog. WRBYTE, WRWORD AND WRLONG will write bytes, words and longs to hub RAM. RDBYTE, RDWORD and RDLONG will read them. I believe that deSilva said that he thought, to his mind, the source and destination fields were reversed. I've gotten it to work properly after playing with it for a while. At least you know it's possible now.

    Sandy
  • AribaAriba Posts: 2,682
    You define an array in a VAR section with the size in brackets.
    Here are the needed snippets for a word array and assembler writes to it:
    VAR
        word  buffer[4096]                'an array with 4096 x 16 bits
    
    PUB Main
        cognew(@AsmCode, @buffer)    'pass first address of buffer to par
        ...
    
    
    DAT
    AsmCode   mov    ptr,par
              mov    n,#4096
    loop      ...
              wrword value,ptr
              add    ptr,#2
              ...
              djnz   n,#loop
              ...
    
    
    Andy
  • Awesome, thanks! That solves that question. BUT....I have a follow on, because it doesn't seem to be working correctly for me, either.

    Suppose I have one cog that is constantly checking the status of something (button press, etc.) and sending that value back to main ram. Now I have a second cog that I want to take samples of that value that the first cog sent back.

    I would THINK it would work just like in Ariba's example, but when I start the second cog, I also send it @buffer in the parameter.
    VAR
        word  buffer[4096]                'an array with 4096 x 16 bits
        word  store
    PUB Main
        par1[0] := store
        par1[1] := buffer
    
        cognew(@AsmCode, @store)   'pass store for @AsmCode to write back to
        cognew(@sampler, @par1)    'pass store (to read from) and buffer (to write samples back to)
    

    But something isn't working. I've verified that the first cog is writing back to store correctly. And I've verified that the second cog can write SOMETHING back to buffer correctly. However, I'm unable to read store in @sampler. I'm parsing PAR correctly, so that's not the problem. I know the whole point of putting stuff back into main ram is to be able to share it between cogs but I am not doing that successfully.

    Thanks for your help so far!
  • Duane DegnDuane Degn Posts: 10,588
    edited 2017-03-08 22:13
    I think there are a couple issues in the code Andy posted (I'm sure he knows this stuff but it must have just slipped his mind).
    Ariba wrote: »
    You define an array in a VAR section with the size in brackets.

    There are times when your example wouldn't work correctly. You need to make sure the address passed with par is long aligned.

    Depending on what other variables are defined in the VAR section, the buffer array may or may not be long aligned.

    There are several ways to insure a word (or byte) array is long aligned. Here are couple of methods I use. Both of these methods define the array in a DAT section.
    buffer       long ' don't assign any value to the long
                    word 0[4096]
    
                   org ' forces long aligned
    buffer       word 0[4096]
    

    Make sure you use another org to start your PASM code so the addressing is done correctly.

    There's also a problem with this bit of code:
    Ariba wrote: »
              mov    n,#4096
    
    

    The value 4096 will need to be assigned to a variable accessed indirectly.
              mov    n, bufferSize
    
     . . .
    
    bufferSize   long 4096
    

    The source field in a PASM statement is limited to 9-bits.

    This last error would have been caught by the compiler.
  • Problem still not solved, but I did notice that I had one var that was a byte so I switched it to a long so i didn't screw up my parameters. I do think something like that is still going on, because when I moved some things around, I was getting the value of a different parameter address being sent back to my "buffer" address.

    So are there any little simple rules that I should know that might be throwing things off? For instance, do I need to do an ORG before DAT sections that will be done by separate cogs? Do my data and reserveds for the different PASM "modules" need to be "with" their module, or all at the bottom of the DAT? I found that if I had one data item at the end (before the RES still) that it did weird things, but when it was just under the section of code for that cog, it worked fine. Should I just throw all my code up here so you can see what I'm doing wrong?
  • One thing from the code I didn't follow was this statement:
    _Parameter[3] := mem
    

    From what I see, the above code will set _Parameter[3] to zero.

    I haven't followed the code closely, but my guess is you intended to pass the address of "mem" rather than the value of the first element.

    Should the line be the following?
    _Parameter[3] := @mem
    
  • Since parameter is passed in cognew as @parameter, I think that suffices. That part seems to work. It's _Parameter[2] that doesn't seem to be getting to the destination. At least not as I expect it to. When I wrlong Counter to the mem location, I get the right values back. When I rdlong the button_value (_Parameter[2]) and then try writing that to mem, I get zero. But I've verified that the value is not really zero.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2017-03-09 18:37
    sbergren wrote: »
    Since parameter is passed in cognew as @parameter, I think that suffices. That part seems to work.

    Sorry for the bad suggestion.

    I'm still not clear what this line of code is supposed to do.
    _Parameter[3] := mem
    

    So others know what we're discussing, here's were the arrays and variables are defined.
    VAR
      LONG _id
      LONG _resources[4]
      LONG _Parameter[3]
      LONG mem[20]
      LONG count2
      long buttons_value
      long i
    

    Below is the code where the array is filled.
      _Parameter[0] := _id
      _Parameter[1] := count2
      _Parameter[2] := buttons_value
      _Parameter[3] := mem
    

    The Parameter array only has three elements so the element "_Parameter[3]" is out of bounds.

    "_Parameter[3]" is another name for "mem[0]".

    As the arrays are defined, the line "_Parameter[3] := mem" doesn't serve any useful purpose.

    Is the array "_Parameter" supposed to have four elements instead of three?

    I'm guessing there's a mistake in the way the "_Parameter" array is used. Hopefully this is a hint towards finding your bug.


  • Grrr. Yes, thanks. I added another parameter but forgot to extend the size. The purpose of mem is to point to the address where I'm going to save the samples.

    That didn't fix my problem, though. Still trying to sample a value and store it to mem.

    The first cog is there just to generate input. Imagine its like an ADC and it stores its current value in "buttons_value." The second cog "Record" is trying to read the value of "buttons_value" at a regular interval and write the value to a memory array.

    The problem seems to be that Record is unable to "see" the current value of "buttons_value" and I'm not sure why. I pass the address of "buttons_value" in _Parameter, so I would think I could just rdlong at that address and get the value. It just doesn't seem to be happening.
  • sbergren wrote: »
    I pass the address of "buttons_value" in _Parameter

    I'm still wondering if you're missing an "@" symbol somewhere.

    Do you know you can "cheat" and poke the values of address into PASM variables instead of using par?

    If you change:
    button_state_address  RES       1
    

    to
    button_state_address  long      0
    

    You can set the value of "button_state_address" before you launch the cog.
      button_state_address := @buttons_value
    

    I'm not sure what you're trying to do, so I'm not sure where you're missing an address symbol but I think you are missing at least one.

    If you attach you're latest code, I'll look at again.
  • I've been struggling with one of my own projects, so I thought I'd take a crack at your code just so you can see another approach.

    I wrote this from scratch, based on what I think you want. It launches a "sampler" cog that will scan the QS pads at a rate that you specify (between 50 and 1000ms). When all the samples are taken, the sampler cog alerts the foreground and shuts itself down. From that point, the demo program plays the samples back at the same rate.

    The program is fully functional. You may find it educational.

    Things to remember:
    -- keep your formatting neat & tidy, using a consistent style (mine mimics C style)
    -- make code (even PASM) as modular as you can so that it's easier to re-use
    -- PASM labels really should be in column 1
    -- when you do a jmp or call you must prefix the label with # (PASM address), else it will treat the value in the variable you reference as an address
    -- when possible, make your code system frequency agnostic
  • sbergrensbergren Posts: 12
    edited 2017-03-21 20:10
    I may be doing something wrong, but I can't get your code to do anything. I'm just loading it to RAM, but I don't get anything from the terminal, no LEDs....not sure what I'm possibly doing wrong, though.

    Additionally, I don't understand something about the code that I think may be the big thing I am missing. You pass scog to sampler. But then you skip over it and get the #samples. How do you know that's at the next long? What sets that structure?
  • In any case, what I'm trying to accomplish is writing to hub ram with one cog, reading from same hub ram location with another cog. I've simplified my code to try and just write the button value to the hub with one cog, and use another cog to read the hub ram and turn on the LEDs. I am failing somehow. LED lighting works correctly if I do it all in one cog. I've commented out that code and have another cog doing that. Only difference is the second cog has to read in the value from hub ram. I know the first cog is writing to hub ram correctly because I'm getting the value sent correctly to the terminal. It's just not being read in by the second cog, apparently. I haven't seen any good examples that show this exact circumstance (write with one, read with another), but it shouldn't be hard. I'm not sure why it doesn't want to work. Anyone's help would be most appreciated.
    CON
    
        ' This uses the Quickstart. Cog 1 is constantly sampling the touch buttons and writing the value to hub RAM.
        ' Cog 2 is reading the hub ram value and lighting the LEDs.  Only purpose is to demonstrate writing/read of hub ram
        ' by the cogs.
    
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
      BUTTONS = $FF
      SAMPLES = 10
      RATE = 1_000
      BUTTON_MASK = $FF
      ADDRESS_INCREMENT_UNIT = 4
    
    VAR
      LONG _id
      LONG _resources0
      LONG _resources1
      LONG _resources2
      LONG _resources3
      LONG buttons_value
    
      
    
    OBJ
      pst : "Parallax Serial Terminal"
    
    PUB Main
    
      _resources0 := buttons & BUTTON_MASK
      _resources1 := samples
      _resources2 := CLKFREQ / rate
      _resources3 := buttons_value
      
      COGNEW(@SETUP, @_resources0)
    
      COGNEW(@LIGHT, @_resources3)
    
      pst.Start(115_200)
      repeat  
        pst.bin(_resources3,8)
        pst.newline
        pst.Str(string("--------"))
        pst.newline
        waitcnt(cnt+80_000_000)
      pst.stop
    
    
    DAT
                            ORG       0
    
      SETUP                 MOV       parameter, PAR                                ' get PAR address
                            RDLONG    user_button_mask, parameter                   ' read mask value
                            ADD       parameter, #ADDRESS_INCREMENT_UNIT            ' move to next par long
                            RDLONG    user_sample_count, parameter                  ' read sample count
                            ADD       parameter, #ADDRESS_INCREMENT_UNIT            ' move to next par long
                            RDLONG    user_sample_rate, parameter                   ' read sample rate
                            ADD       parameter, #ADDRESS_INCREMENT_UNIT            ' move to next par long
                            MOV       button_state_address, parameter               ' get par address for button value storage
                            MOV       OUTA, user_button_mask                        ' set button values to 1s
                            MOV       wait_time, CNT                                ' set up timer
                            ADD       wait_time, user_sample_rate
    
      NEXT_STATE            MOV       current_button_state, user_button_mask        ' init current_button_state with 1s
                            MOV       current_sample_count, user_sample_count       ' store sample count
    
                            OR        DIRA, user_button_mask                        ' set buttons to outputs * this is how these resistive
                            ANDN      DIRA, user_button_mask                        ' set to inputs          * touch buttons work
                            OR        dira, LED_MASK                                ' set LEDs as outputs to correct what previous inst did 
                            WAITCNT   wait_time, user_sample_rate                   ' wait for next sample
                            ANDN      current_button_state, INA                     ' read button state
                            SUB       current_sample_count, #1                      ' dec sample counter
    
      NEXT_SAMPLE           MOV       previous_button_state, current_button_state   ' store previous button state
                            MOV       DIRA, user_button_mask                        ' same cmds as before to get button state
                            ANDN      DIRA, user_button_mask                        '
                            OR        dira, LED_MASK                                '
                            WAITCNT   wait_time, user_sample_rate                   '
                            ANDN      current_button_state, INA                     '
                            CMP       previous_button_state, current_button_state WZ' compare previous and current states
                    IF_NE   JMP       #NEXT_STATE                                   ' if not eq return to next_state
                            DJNZ      current_sample_count, #NEXT_SAMPLE            ' get another sample if not yet at sample count
                            WRLONG    current_button_state, button_state_address    ' if 'sample count' number of consecutive samples 
    '                        MOV       button_state, current_button_state           ' are the, then readings were accurate and value
    '                        SHL       button_state, #16                            ' is  written to hub ram
    '                        MOV       LED_state, outa                              
    '                        ANDN      LED_state, LED_MASK                          ' The commented out code was doing the work
    '                        OR        LED_state, button_state                      ' the second cog should be doing now.
    '                        MOV       outa, LED_state
                            JMP       #NEXT_STATE
    
      LED_MASK              long      $FF<<16
      button_state_address  RES       1
      current_button_state  RES       1
      current_sample_count  RES       1
      parameter             RES       1
      previous_button_state RES       1
      user_button_mask      RES       1
      user_sample_count     RES       1
      user_sample_rate      RES       1
      wait_time             RES       1
    '  button_state          RES       1
    '  LED_state             RES       1 
    
                            ORG       
      LIGHT                 mov       button_val_addr, PAR                          ' get button value address from par
     :loop                  rdlong    button_val, button_val_addr                   ' read button value from hub ram
                            SHL       button_val, #16                               ' shift left to align with LED pins
                            mov       temp, outa                                    ' get current output value
                            ANDN      temp, LED_MASK1                               ' mask out LED bits
                            OR        temp, button_val                              ' or the shifted button value
                            mov       outa, temp                                    ' set outa to the new LED values
                            jmp       #:loop                                        ' loop to next read of button value
    
    
      LED_MASK1             long      $FF<<16 
      temp                  RES       1
      button_val_addr       RES       1
      button_val            RES       1
      
      fit
    

  • VBBVBB Posts: 52
    edited 2017-03-22 21:48
    _resources3 := @buttons_value 'Make resources a pointer ie contain the address of the allocated buttons_value 
     
    
      LIGHT                MOV       parameter, PAR                                     ' get PAR address
                               rdlong    button_val_addr  , parameter                   ' read the pointer value from resources3
    
    
     :loop                  rdlong    button_val, button_val_addr                   ' read button value from hub ram
    
  • I'd tried that before. That doesn't fix it.
  • potatoheadpotatohead Posts: 10,253
    edited 2017-03-23 15:11
    . :D







  • Yes, you're doing something wrong -- I tested that code very carefully and it works well. It demonstrates write to memory from one cog, reading from another -- just not at the same time and this is of little consequence. Perhaps it's time to do what I did: start from scratch. Your current approach is not working for you. Starting over is an option that I give myself when a piece of code is not behaving the way I'd like it to.
  • AribaAriba Posts: 2,682
    edited 2017-03-23 10:24
    The main problem of your code is that you don't set the DIRA for the LED outputs in the second cog. Every cog has it's own DIRA register. If several cogs drive the same pin as output, they get ORed together, so you also should not set the LED pins to output in the first cog, if you don't use it there.

    Beside that, the button sample methode you use seems to not work well for my fingers.
    So I have made a simplified version of your code with my own way to read the buttons:
    CON
    
        ' This uses the Quickstart. Cog 1 is constantly sampling the touch buttons and writing the value to hub RAM.
        ' Cog 2 is reading the hub ram value and lighting the LEDs.  Only purpose is to demonstrate writing/read of hub ram
        ' by the cogs.
    
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
    
    VAR
      long  buttonMask, buttonDly, buttonVal
    
    OBJ
      pst : "Parallax Serial Terminal"
    
    PUB Main
    
      buttonMask := $00FF
      buttonDly  := clkfreq/1000         '1ms
      
      cognew(@setup, @buttonMask)
      cognew(@light, @buttonVal)
    
      pst.Start(115_200)
      repeat  
        pst.bin(buttonVal,8)
        pst.newline
        pst.Str(string("--------"))
        pst.newline
        waitcnt(cnt+80_000_000)
      pst.stop
    
    
    DAT
                          ORG       0
    
    setup                 MOV       adr, PAR                                ' get PAR address
                          RDLONG    user_button_mask, adr                   ' read mask value
                          ADD       adr, #4                                 ' move to next par long
                          RDLONG    user_sample_rate, adr                   ' read sample rate
                          ADD       adr, #4
                          MOV       button_state_address, adr               ' get par address for button value storage
                          MOV       OUTA, user_button_mask                  ' set button values to 1s
                          MOV       wait_time, CNT                          ' set up timer
                          ADD       wait_time, user_sample_rate
    
    getState              OR        DIRA, user_button_mask                        ' set buttons to outputs * this is how these resistive
                          ANDN      DIRA, user_button_mask                        ' set to inputs          * touch buttons work
                          WAITCNT   wait_time, user_sample_rate                   ' wait button delay time
                          MOV       current_button_state, INA                     ' read button state
                          XOR       current_button_state, user_button_mask        ' state = invers of read
                          AND       current_button_state, user_button_mask        ' mask buttons
                          WRLONG    current_button_state, button_state_address    ' write state to buttonVal var 
                          ANDN      OUTA, user_button_mask                        ' discharge button caps
                          OR        DIRA, user_button_mask
                          WAITCNT   wait_time, user_sample_rate                   ' wait button delay time
                          OR        OUTA, user_button_mask                        ' all 1 for next sampling
                          JMP       #getState
    
    button_state_address  RES       1
    current_button_state  RES       1
    adr                   RES       1
    user_button_mask      RES       1
    user_sample_rate      RES       1
    wait_time             RES       1
    
    
    DAT
                          ORG       0
    light                 mov       DIRA, LED_MASK                                ' set LED pins to output
    :loop                 rdlong    button_val, par                               ' read button value from hub ram
                          shl       button_val, #16                               ' shift left to align with LED pins
                          mov       OUTA, button_val                              ' set outa to the new LED values
                          jmp       #:loop                                        ' loop to next read of button value
    
    LED_MASK              long      $FF<<16 
    button_val            RES       1
    
                          fit
    

    This works on my Quickstart board.

    Andy
  • sbergrensbergren Posts: 12
    edited 2017-03-23 13:03
    Andy, thanks! It was the dira problem. I almost did that, but thought, nah, the other cog still sets it back up, I don't need it here. But, apparently I did. As for the button sampling, I just found that out in the obex and it seems to do fine for me.

    Johnny, I'm going to try yours out again. I didn't change anything in it and assumed I didn't need to. But you know what they say when you assume...

    (Edited: Oh, yeah, I figured it out. Open the terminal and hit a button. DOH!)
Sign In or Register to comment.