Shop OBEX P1 Docs P2 Docs Learn Events
Head Spinning Around JMPRET — Parallax Forums

Head Spinning Around JMPRET

JonnyMacJonnyMac Posts: 9,104
edited 2017-02-18 17:49 in Propeller 1
Those who know me know I'm a pretty regular guy who writes straightforward -- easy for my fellow actors to understand -- code. I have happily used FDS for years without worrying about the mechanics. Until now. I want (hence need) a simple FDS UART that can also run a 1ms timer.

I do tend to swallow my own medicine so I read what I could about JMPRET and even created a project with three LEDs that happily blink at independent rates. Next I converted one of those LED processes to a timer process that updates a hub variable and it works great.

So what's the problem? Me. Every time I look at the way I've set things up my mind gets into a twist thinking it's going to break somewhere. Here's my skeletal code for three processes:
dat

entry                   mov     p1code, #proc1
                        mov     p2code, #proc2
                        mov     p3code, #proc3

                        ' other setup
                        

proc1                   jmpret  p1code, p2code

                        ' run process 1

                        jmp     #proc1


proc2                   jmpret  p2code, p3code

                        ' run process 2

                        jmp     #proc2


proc3                   jmpret  p3code, p1code

                        ' run process 3

                        jmp     #proc3
My mind thinks there's a lurking landmine. Is there, or am I seeing things being tired and stressed about another big project?

Comments

  • tonyp12tonyp12 Posts: 1,951
    edited 2017-02-18 18:38
    jmpret is like GOSUB.
    But as the Cog don't have any stack, you have to include the destination of the jmp that ends this subroutine as this instruction will self-modify that 9bit address of current PC+1

    https://www.parallax.com/sites/default/files/downloads/AN014-Coroutines-v1.0.pdf

    p.s. jmp is just a jmpret with no write flag set.
    '-------[ Main Program ]----------------------------------------- 
     
                  ...               
                  jmpret    subr_ret,#subr   'Call subr.               
                  ... 
     
    '-------[ Subroutine ]-------------------------------------------                                              
    subr                                      'Subroutine entry point.              
                  ...                                      
    subr_ret      jmp       #0-0              'Return to caller...
    

  • ke4pjwke4pjw Posts: 1,155
    edited 2017-02-19 04:04
    That ping pong multitasking is a head scratcher. In this case, is it ping pong pang?

    I suspect you could lose some bits on RX if you created a 1ms pause at higher baud rates and were transmitting at the same time. What's the baud rate?

  • Tracy AllenTracy Allen Posts: 6,664
    edited 2017-02-19 01:41
    I don't see where you've defined p1code, p2code and p3code as addresses. That could be done as DAT, as it is in fullduplexserial, or it can be done similar to the way you have it but with addresses all at the top of the pasm code after entry:
    entry
    p1code  if_never        mov     p1code, #proc1
    p2code  if_never        mov     p2code, #proc2
    p3code  if_never        mov     p3code, #proc3
    
    The if_never effectively turns the instructions into NOPs, but the source field is left with the addresses of the initial coroutines. The source field is the only one that participates in the jmpret process.
    The plot usually thickens with additional segments in each process. That is where jmpret shows its stuff, otherwise you might as well execute the tasks in sequence. For each process it will be something like:
    proc1A                  jmpret  p1code, p2code
                            ' run part A of proc1
                            if conditionA 
                               jmp #proc1A
    proc1B                  jmpret p1code, p2code
                            ' run part B of proc1
                            if conditionB 
                               jmp #proc1B
    proc1Z                   jmpret p1code, p2code
                            ' run part Z of proc1, final cleanup
                            jmp  #proc1A
    
  • Heater.Heater. Posts: 21,230
    JMPRET certainly is a head scratcher. Elegant but confusing if you have never worked with it before.

    Basically JMPRET is a GOTO. It does a GOTO some address, call it "targetAddr". However it also saves the address of the following instruction some place, call it "returnAddr".

    In this way you can set up two loops. Call them "A" and "B". Loop A runs around and can do a JMPRET into loop B at any time, even into some place in the middle of the loop. Similarly loop B can do JMPRET to returnAddr which gets you back to continuing loop A from where ever it jumped out.

    If I'm making sense there.

    In FDS there are two such loops, RX and TX, that do just that.

    This used to be known as coroutines back in the day. A neat way to make threads. Kind of.

    It's not clear to me if/how you can extend that to three cooperating loops with out introducing some kind of scheduler loop in the middle. Gets complex, messy and slow.

    Perhaps it's best not to mess with the JMPRETs in FDS but just add your 1ms handling to the RX or TX threads.

    Whatever you do it will take time, cause jitters in the timing and limit the usable baud rates.
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2017-02-18 19:11
    @heater,
    jmpret is all about (the appearance of) cooperative scheduling. A coroutine can read and act on CNT or it can manipulate its cog counters.
  • Heater.Heater. Posts: 21,230
    I'm not sure what you mean by "appearance of...cooperative scheduling". If one of my loops does not do any JMPRETs no other loop can run. It is totally cooperative. There are no interrupts on a P1 so preemptive scheduling is not possible.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2017-02-18 20:28
    The only time a jmpret is useful in cooperative multitasking is when at least one task has multiple reentry points. If all tasks run to completion or hinge on a single reentry before switching, there's really no point in using jmprets.

    -Phil
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2017-02-18 20:43
    As a matter of style, I think jmpret multitasking is easier to read when the reentry vectors are placed at the beginning of their respective tasks, viz:
    DAT
    
    taska         jmp       #taska0
    taska0        ...
                  jmpret    taska,taskb
    taska1        ...
                  jmpret    taska,taskb
                  jmp       #taska0
    
    taskb         jmp       #taskb0
    taskb0        ...
                  jmpret    taskb,taska
    taskb1        ...
                  jmpret    taskb,taska
    taskb2        ...
                  jmpret    taskb,taska
                  jmp       #taskb0
    
    -Phil
  • Heater.Heater. Posts: 21,230
    The use of JMPRET in FullDuplexSerial is like this:
    while(1)
    {
        // Do stuff
        suspend();
        // Do more stuff
        suspend();
        // Do even more stuff
        suspend();
    }
    while(1)
    {
        // Do stuff
        suspend();
        // Do more stuff
        suspend();
        // Do even more stuff
        suspend();
    }
    
    Sorry for the C syntax. Think of it as pseudo code.

    In the above "suspend()" gets you out of the infinite while loop and into another. "ping pong" as mentioned above.

    The whole point here is that you can write two totally independent infinite loops (threads). The logical flow of one, as you read it, does not depend on the other.

    Except for that pesky suspend() thing that you have to sprinkle around.

    JMPRET is that suspend().

    Arguably FullDuplexSerial can be written without such coroutines. In fact I seem to recall there is a serial driver that does that and performs better.



  • Heater,

    That's the more traditional approach, but it only works with two tasks using jmpret (i.e. ping-pong fashion). It relies upon the src field of the jump vector being written after it's read. If you have more than two, I think you're stuck with something like my example above, which can be extended to as many tasks as you want.

    -Phil
  • Heater.Heater. Posts: 21,230
    Yes, traditional. Easy for two tasks.

    I was thinking that if you want many tasks then you need a supervisor in the middle. All the tasks JMPRET to their coroutine, which is the supervisor. But the supervisor can juggle return addresses and JMPRET to some other task. I have not thought it through much.

    Actually at first glance I don't see how your example can be extended to more than two tasks. It looks like the PASM version of my pseudo code. No doubt I'm missing a point.

  • heater wrote:
    Actually at first glance I don't see how your example can be extended to more than two tasks.
    taska         jmp       #taska0
    taska0        ...
                  jmpret    taska,taskb
    taska1        ...
                  jmpret    taska,taskb
                  jmp       #taska0
    
    taskb         jmp       #taskb0
    taskb0        ...
                  jmpret    taskb,taskc
    taskb1        ...
                  jmpret    taskb,taskc
    taskb2        ...
                  jmpret    taskb,taskc
                  jmp       #taskb0
    
    taskc         jmp       #taskc0
    taskc0        ...
                  jmpret    taskc,taska
    taskc1        ...
                  jmpret    taskc,taska
                  jmp       #taskc0
    
    -Phil
  • Heater.Heater. Posts: 21,230
    Phil,

    Got it. Cool. You are chaining the tasks, round robin style.

    Not quite like my pseudo code. It requires every task to know about another peer to JMPRET to. In the right order. But so what.

  • Tracy AllenTracy Allen Posts: 6,664
    edited 2017-02-24 04:23
    To me, locating the vectors all together at the top of the pasm code was a mental aid while getting my head around the 4-port serial object.

    When they are all together at the top of the cog, the vectors addresses are simply 0, 1, 2, 3 ... out to the number of separate processes. Suppose there are 4 processes each with several sections. The jmprets look like this...
    jmpret 0, 1   ' for all jmprets in the process A
    jmpret 1, 2   ' for all jmprets in the process B
    jmpret 2, 3   ' for all jmprets in the process C
    jmpret 3, 0   ' for all jmprets in the process D
    
    Of course the vectors will in practice be symbolic names like p0code...p3code, but that is how they evaluate to numbers when they are positioned at the top of the cog memory.
    It is not so hard to understand. Execution of the first jmpret in process A stores the address of its own next instruction to be executed at address 0, then the execution jumps to the address stored at 1. That is in process B. So on around the circle, until in process D the jmpret 3,0 stores the address of its own next instruction at address 3 and the execution jumps back to the address that was just recently stored at address 0 for process A. It is important to note that the contents of the jmpret instructions do not self-modify. Only the vectors change as the sections of each coroutine execute. If process A has three sections, then at different times address 0 will point to and vector to one of three code addresses in process A as those sections come into play.

    Consider the 4-port serial object. It has 8 coroutines, each with 3 sections, a total of 24 jmpret instructions. There are 8 vectors. A complication is that its initialization has to selectively enable or disable any of the 8 coroutines. The application might require only 2 1/2 serial ports, not all 4. To do that, the initialization has to patch the jmpret instructions. In the above example with 4 processes, suppose it is necessary to disable process B, The initialization has to patch all the jmprets in process A to read,
    jmpret 0, 2   ' for all jmprets in the process A, now skips process B, jumps to C instead.
    
    The 4-port object contains 8 separate processes, threaded like this...
    jmpret 0, 4   ' for all jmprets in the rx0 process
    jmpret 1, 5   ' for all jmprets in the rx1 process
    jmpret 2, 6   ' for all jmprets in the rx2 process
    jmpret 3, 7   ' for all jmprets in the rx3 process
    jmpret 4, 1   ' for all jmprets in the tx0 process
    jmpret 5, 2   ' for all jmprets in the tx1 process
    jmpret 6, 3   ' for all jmprets in the tx2 process
    jmpret 7, 0   ' for all jmprets in the tx3 process
    
    The order of threading does not matter so long as it completes the circle.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2017-02-19 02:14
    I'm intrigued by Heater's pseudocode example of a common suspend() function. I think such a function would be possible for more than two tasks -- albeit with some extra overhead that compares the return address with address ranges for the various tasks and then movs that address into the proper transfer vector. suspend could then regulate the cycling among the various tasks as well, without those tasks having to individually do that themselves.

    I think this would do the job:
    taska         ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  jmp       #taska
    
    taskb         ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  jmp       #taskb
    
    taskc         ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  ...
                  jmpret    v,#suspend
                  jmp       #taskc
    
    suspend       cmp       v,#taskb wc
            if_c  mov       vectora,v
            if_c  jmp       vectorb
                  cmp       v,#taskc wc
            if_c  mov       vectorb,v
            if_c  jmp       vectorc
                  mov       vectorc,v
                  jmp       vectora
    
    v             long      0
    vectora       long      taska
    vectorb       long      taskb
    vectorc       long      taskc      
    

    -Phil
  • pjvpjv Posts: 1,903
    Tracy and Phil;

    If you take concepts you have described a couple of steps farther, and make the JMPRETs from the individual tasks jump to a common entry point in a scheduler which then dispatches the next-to-be-executed thread, then you can get a very effective albeit basic "RTOS".

    I have written such a scheduler, and it can run 8 (or theoretically more) individual threads in a single cog very nicely, all without regard for each others' sequence or timing. The basic kernel is only 34 instructions long and has an overhead of about 2 usec plus 1/3 usec per active thread. The technology dates back almost 10 years, and has evolved considerably since that time.

    Some enhanced features, (with more code) permit you SUSPEND and RESUME operation of any thread, from any other thread. It also polls a mailbox in hub and can execute commands such as upload, download and others from SPIN routines that are running in other cogs, all while the threads' sequence and timing are largely unaffected.

    There is a caveat however, and that is some jitter is caused by individual threads competing for the cog's CPU at the same instant. This typically is not an issue except at high baud rates (>115200) and having many threads running simultaneously.

    The Propeller is such a wonderful machine to let us do all these neat functions.

    I would consider publishing this code for use in non commercial applications.

    Cheers,

    pjv (Peter)

  • That sounds interesting. Is your mini-RTOS in OBEX?
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2017-02-19 03:55
    Peter, I was thinking about your RTOS and the nice presentation that you gave about it at one of the Parallax good-ol-days get togethers. It is good to hear that is still going strong and evolving and now even supports Spin cog integration. I've certainly forgotten the details and would like to hear more. I'm unlikely to need it for a commercial application, usually being content with 8 cogs and ad-hoc fiddling. Maybe another thread?

    Jon? You asked about straightforward code, the mechanics of jmpret in order to add a 1ms timer to fds. The 1ms timer should indeed be an easy add-on to fds, provided there is either a little tolerance in how close the duration has to be to exactly one ms, or, the baud rate is low enough that the whole cycle can be synchronized to cnt in a way that brings it to 1ms right on the dot. The latter would be something like an RTOS. Something makes me suspect that you wants to be your own Actor to roll out the code.
  • pjvpjv Posts: 1,903
    David,

    No it is not. A requirement for posting in OBEX is the open MIT license, and I am not in agreement with that in respect of commercial applications.

    In discussing this with Ken, his alternate suggestion was to publish it as an app note, where the commercial restriction conditions could be specified.

    And this I have not bothered to do.

    I think if there is enough interest for personal applications, I could do that.

    Cheers,

    Peter (pjv)
  • Heater.Heater. Posts: 21,230
    That "while () { ..... suspend()...}" pseudo code I presented above comes from a small cooperative scheduler we wrote for a communications project running on x86 back in the late 1980's. Written in PL/M 86 it ran on our target hardware and PC under MS-DOS. It was called MASCOT (Modular Approach to Software Construction Operation and Test) as a simplified version of the design methodology laid down by The Royal Signals and Radar Establishment in "The Official Handbook of MASCOT." See Section 4.4 "Scheduling and Priorities". http://async.org.uk/Hugo.Simpson/MASCOT-3.1-Manual-June-1987.pdf

    Our scheduler's suspend worked by juggling with the return address and such on the stack. The scheduler maintained simple queues of ready and waiting tasks. It had a simple round robin scheduling scheme. We had other primitives like "wait" and "delay". Because we used the stack those suspends could be buried deep in a call stack and still work, unlike the JMPRET scheduling.

    One sneaky thing it had was the ability to read/write disk without blocking. It would schedule tasks to run while the disk access was going on behind the scenes. Took some juggling with BIOS interrupts to do that.

    I'm not sure most of the world would call such a cooperative scheduler an RTOS. "Real Time Operating System" implies strict timing deadlines that cooperative scheduler cannot really provide. Usually involving interrupts and priorities. Of course in the Propeller we achieve real-time guarantees by using dedicated cores.

    @pjv,

    Why is the MIT license a sticking point? Seems unlikely any commercial enterprise is going get rich just by using your code. Even less likely they would want to pay you for use of it. If they ever needed such a scheduler they could make their own easily enough. Especially given Phil's inspiration above. I would have though the benefit to the Propeller community, mostly non-commercial use, would out way such concerns. And think of the fame, respect and standing in the community you would earn!
  • To be clear, my blinkers plus 1ms timer were working -- have enjoyed watchin this thread and it did confirm my three-task setup as correct. Here's the dirt-simple time update.
    ms_timer                jmpret  mscode, f1code
                            mov     elapsed, mscnt
                            sub     elapsed, cnt
                            cmps    elapsed, #0             wc
            if_nc           jmp     #ms_timer
                            rdlong  t1, par
                            add     t1, #1
                            add     mscnt, mstix   
                            wrlong  t1, par
                            jmp     #ms_timer
    
    As you can see, it is derived from the bit-timing code used in FDS (using ticks per ms passed via cognew).

    Again, I was concerned about my overall structure, even though my initial test code was working just fine. I'm not worried anymore, and my serial code will actually be simpler than what's in FDS because I only need True mode, and there are no circular buffers required -- just a one-byte command [tx] an a one-byte status [rx]. I will probably add a running flag when a start command is issue that will allow the timer to increment, and will clear and stop the timer when an EOF comes back from the player.
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-02-20 18:32
    Good news: I have my specialty UART with timer working with the target video player. It's an inexpensive player, hence not very sophisticated in its interface. While playing it spits out the current file (0..200) as a byte, or $EE when it hits the end of the file. I am resetting my milliseconds timer when I rx an $EE, or transmit a new file number to play.

    There is a serial PAUSE command -- unfortunately, it's a toggle (would prefer to have separate RESUME). I'm going to XOR a bit

    Here's the PASM -- please remember that this is a WIP; your suggestions are appreciated, and please go easy on me.

    Updated 20 FEB 2017
    dat  { unbuffered fd uart with timer }
    
                            org     0
    
    entry
    rxcode  if_never        mov     rxcode, #receive                ' assign task addresses 
    txcode  if_never        mov     txcode, #transmit                       
    mscode  if_never        mov     mscode, #ms_timer
    
                            rdlong  t1, par                         ' get pins & bit timing
                            mov     t2, t1                          ' make a copy
    
                            and     t1, #$1F                        ' setup rx
                            mov     rxmask, #1
                            shl     rxmask, t1
    
                            mov     t1, t2                          ' setup tx
                            shr     t1, #8
                            and     t1, #$1F   
                            mov     txmask, #1 
                            shl     txmask, t1
                            or      outa, txmask                    ' set tx to idle
                            or      dira, txmask
    
                            mov     bittix, t2                      ' setup bittix
                            shr     bittix, #16
    
                            mov     rxhub, par                      ' setup hub addresses
                            add     rxhub, #2                       ' rx to buffer.byte[2]
                            mov     mshub, par
                            add     mshub, #4
    
                            rdlong  mstix, mshub                    ' get ticks/ms
    
                            mov     t1, #EOF                        ' initialize buffer & timer
                            shl     t1, #16
                            wrlong  t1, par
                            
                            mov     msruntime, #0                   ' clear hub timer
                            wrlong  msruntime, mshub
              
                            mov     flags, #%000                    ' not playing now
                                    
    
    ' -----------------                                              
    '  Receive Process                                               
    ' ----------------- 
    
    receive                 jmpret  rxcode, txcode
                                       
                            test    rxmask, ina             wc      ' look for start bit      
            if_c            jmp     #receive                         
                                                                     
                            mov     rxbits, #9                      ' ready to receive byte
                            mov     rxcnt, bittix                    
                            shr     rxcnt, #1                        
                            add     rxcnt, cnt                           
                                                                      
    :bit                    add     rxcnt, bittix                   ' ready next bit period
                                                                      
    :wait                   jmpret  rxcode, txcode      
                                                                      
                            mov     t1, rxcnt                       ' check if bit receive period done
                            sub     t1, cnt                           
                            cmps    t1, #0                  wc        
            if_nc           jmp     #:wait                            
                                                                      
                            test    rxmask, ina             wc      ' receive bit on rx pin
                            rcr     rxdata, #1                       
                            djnz    rxbits, #:bit                    
                                                                     
                            shr     rxdata, #32-9                   ' justify and trim received byte
                            and     rxdata, #$FF                    ' clean-up
                            cmp     rxdata, #200            wz, wc  ' check for valid file
            if_a            jmp     #:end_play                      ' check for EOF/special status
    
                            wrword  rxdata, rxhub                   ' write file, clear special
    
                            jmpret  rxcode, txcode
                            
                            test    flags, #%100            wc      ' save pause flag
                            shl     flags, #1                       ' make room for update
                            or      flags, #1                       ' still playing
                            and     flags, #%11                     ' clean-up
                            cmp     flags, #%01             wz      ' new start?
                            muxc    flags, #%100                    ' restore pause flag   
            if_ne           jmp     #receive
           
                            mov     mscnt, cnt
                            add     mscnt, mstix
                            mov     msruntime, #0  
                            wrlong  msruntime, mshub                                               
                            jmp     #receive
    
    :end_play               cmp     rxdata, #EOF            wz      ' end-of-file?
            if_ne           jmp     #:special
    
                            wrword  rxdata, rxhub                   ' EOF, clear special
                            mov     flags, #%000
                            jmp     #receive
    
    :special                mov     t1, rxhub
                            add     t1, #1
                            wrbyte  rxdata, t1                      ' write to buffer.byte[3]
                            jmp     #receive
    
    
    ' ------------------                                              
    '  Transmit Process                                               
    ' ------------------ 
    
    transmit                jmpret  txcode, mscode
    
                            rdlong  txdata, par                     ' check for command
                            test    txdata, HAS_CMD         wc
            if_nc           jmp     #transmit
    
                            wrword  ZERO, par
    
                            mov     txlast, txdata                  ' save clean copy
                            and     txlast, #$FF      
    
                            or      txdata, STOP_BITS               ' add stop bit(s)
                            shl     txdata, #1                      ' add start bit
                            mov     txbits, #11                     ' bits = start + 8 + 2 stop
                            mov     txcnt, cnt                      ' start bit timer 
                          
    :bit                    shr     txdata, #1              wc      ' get bit from txdata    
                            muxc    outa, txmask                    ' move to tx pin                   
                            add     txcnt, bittix                   ' ready next cnt
                                                                     
    :wait                   jmpret  txcode, mscode
                                                                     
                            mov     t1, txcnt                       ' check if bit transmit period done
                            sub     t1, cnt                          
                            cmps    t1, #0                  wc       
            if_nc           jmp     #:wait                           
                                                                     
                            djnz    txbits, #:bit                   ' another bit to transmit?
                                
                            cmp     txlast, #200            wc, wz  ' file command?
            if_a            jmp     #:chk_pause
    
                            mov     mscnt, cnt                      ' restart timing on new file cmd          
                            add     mscnt, mstix         
                            mov     msruntime, #0        
                            wrlong  msruntime, mshub     
                            jmp     #transmit
                                     
    :chk_pause              cmp     txlast, #KEY_PAUSE      wz
            if_e            xor     flags, #%100                    ' toggle pause flag
                            jmp     #transmit 
     
    
    ' -------------------
    '  1ms Timer Process
    ' -------------------
                            
    ms_timer                jmpret  mscode, rxcode
                            mov     t1, mscnt
                            sub     t1, cnt
                            cmps    t1, #0                  wc
            if_nc           jmp     #ms_timer
                            add     mscnt, mstix                    ' set new target 
                            test    flags, #%100            wc      ' set c if paused
                            test    flags, #%011            wz      ' set z if not playing
            if_nc_and_nz    add     msruntime, #1                   ' running -- inc ms
                            wrlong  msruntime, mshub
                            jmp     #ms_timer
    
    
    ' --------------------------------------------------------------------------------------------------
    
    ZERO                    long    0
    HAS_CMD                 long    $1_00
    STOP_BITS               long    $FFFF_FF00
    
    bittix                  res     1                               ' ticks per bit
                                                                     
    rxmask                  res     1                               ' mask for rx pin
    rxhub                   res     1                               ' hub address of rxbuffer                             ' 
    rxdata                  res     1                               ' received byte
    rxbits                  res     1                               ' bit counter for rx
    rxcnt                   res     1                               ' bit timer for rx
                                                                     
    txmask                  res     1                               ' mask for tx pin
    txdata                  res     1                               ' byte to transmit
    txlast                  res     1                               ' last byte transmitted
    txbits                  res     1                               ' bit counter for tx
    txcnt                   res     1                               ' bit timer for tx
    
    mshub                   res     1                               ' hub address of ms
    mstix                   res     1                               ' ticks/millisecond
    msruntime               res     1                               ' run time of current file in ms
    mscnt                   res     1                               ' timer for 1ms
    
    flags                   res     1
                                                                     
    t1                      res     1                               ' working registers
    t2                      res     1                                
                            
                            fit     496
    
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2017-02-20 17:39
    In the transmit routine, the second jmpret threads back through rxcode rather than through mscode. Maybe that is a typo, or maybe intentional. The program will work either way, but I thought I'd mention it in the discussion about jmpret because it gives the receive routine priority over the millisecond timer in that wait loop. It shows that there is no requirement for all the sections of a coroutine to thread to the same place.

    About the serial receive coroutine, once it receives one of the command characters it takes quite a long time to process the command before it gets back to test the other coroutines. It might mess up (depending on the baud rate) If it happened to be in the middle of transmitting a command at the same time that it received one, full duplex. However, I suspect that may never happen in this controller. It works, yeh!
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-02-20 18:56
    Thanks for the catch, Tracy -- was a line from FDS that I didn't modify. I added another JMPRET line to the receive routine per your implied suggestion. The device is slow: 2400, 4800, 9600, 19.2K, and 38.4K. I've been testing with 38.4K and am not having any problems.

    When I first started playing with it I connected it to a Propeller with FDS and just watched the status returns. The player will report the current file at about a 12 Hertz rate -- "about" being the key word. The gentleman who sells the product in the US has a lot of experience with the BASIC Stamp so he made it (working with off-shore engineers) Stamp friendly. I have some customers who can benefit from a device like this using EFX-TEK controllers. The player loops its "zero" file, can play any other file, then goes back to file zero. I wanted to add a timer to facilitate chaining files based on time. Admittedly, this is a bit of an exercise for me, but I think it will be useful; with this object I don't have to worry about creating a timer elsewhere.
Sign In or Register to comment.