Shop OBEX P1 Docs P2 Docs Learn Events
Is there a good tutorial on using FullDuplexSerial to receive data? — Parallax Forums

Is there a good tutorial on using FullDuplexSerial to receive data?

Robert GrahamRobert Graham Posts: 55
edited 2018-04-09 12:23 in Propeller 1
It's very simple to send data. And the setup instructions for the serial LCD screens make that very clear. I didn't have any trouble with that.
https://www.mouser.com/datasheet/2/321/27979-Parallax-Serial-LCDs-Product-Guide-v3.1-336825.pdf

But receiving data is another matter. Do I loop one of the methods to look for incoming data? What do I have to add to the Con section? Inquiring minds want to know.

This is code that everyone and their dog uses (if the dog has a propeller), and yet I can't google up a decent tutorial on how to use it. I wouldn't even know how to send with it, had I not purchased a serial screen years ago.

The simplicity of sending with FDS makes me think it won't be that difficult. Where is this info?

I just want to send a few dozen bytes between propellers. It would be nice if I could also send and receive strings. I saw on a another thread that FDS can't receive strings, so maybe I should look at another object?

As usual, love the hardware, but not the mysterious code that I don't understand. I am not a professional software engineer, and so it isn't easy for me to reverse engineer some one else's code.

I need to be able to send or receive at up to 40k baud, as MIDI runs at at about 31600. The devices need to be MIDI compatible eventually. I am not sure if Spin code is going to be able to handle that. But I have seen some threads where people have run FDS at 300k or faster. But again, can it receive strings?

Thank you in advance for any help!
«1

Comments

  • kwinnkwinn Posts: 8,697
    It's very simple to send data. And the setup instructions for the serial LCD screens make that very clear. I didn't have any trouble with that.
    https://www.mouser.com/datasheet/2/321/27979-Parallax-Serial-LCDs-Product-Guide-v3.1-336825.pdf

    But receiving data is another matter. Do I loop one of the methods to look for incoming data? What do I have to add to the Con section? Inquiring minds want to know.

    This is code that everyone and their dog uses (if the dog has a propeller), and yet I can't google up a decent tutorial on how to use it. I wouldn't even know how to send with it, had I not purchased a serial screen years ago.

    The simplicity of sending with FDS makes me think it won't be that difficult. Where is this info?

    I just want to send a few dozen bytes between propellers. It would be nice if I could also send and receive strings. I saw on a another thread that FDS can't receive strings, so maybe I should look at another object?

    As usual, love the hardware, but not the mysterious code that I don't understand. I am not a professional software engineer, and so it isn't easy for me to reverse engineer some one else's code.

    I need to be able to send or receive at up to 40k baud, as MIDI runs at at about 31600. The devices need to be MIDI compatible eventually. I am not sure if Spin code is going to be able to handle that. But I have seen some threads where people have run FDS at 300k or faster. But again, can it receive strings?

    Thank you in advance for any help!

    Yes, you do have to loop to receive the individual bytes until all of it is received. There is a built in receive buffer to avoid dropping bytes, but that can overflow if enough data comes in faster than your code can receive it. Using it for MIDI should not be a problem.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2018-04-09 15:46
    It all depends on what your incoming data looks like. For example, I'm working on a project (road sign controller) that expects aperiodic messages from a master. The command message has a header and specific formatting. From the main loop, I call this method:
    pub check_message | idx, c, cs
    
      idx := 0
    
      c := serial.rxcheck
      if (c <> $7E)                                                 ' 1st byte of header
        return 0
      else
        cmd[idx++] := c                                             ' capture header
      
      repeat                                                        ' capture rest of packet
        c := serial.rxtime(2)
        if (c => 0)
          cmd[idx++] := c
        else
          quit
    
      if (idx <> (cmd[2]+4))                                        ' good packet length?
        return 0
    
      cs := crc8x(@cmd, (cmd[2]+3))                                 ' compare checksums
      if (cs <> cmd[cmd[2]+3])  
        return 0 
    
      process_packet
    
      return true
    
    But again, can it receive strings?
    Yes. In another project, I'm actually waiting for a command string sent through a terminal. In this I use code like this:
    pub get_command
    
      repeat
        c := serial.rxcheck                                         ' check for character
        if (c => 0)                                                 ' if present
          if (c <> 13)                                              ' if not at end of command
            cmd[idx++] := c                                         ' add character to string
          else
            cmd[idx] := 0                                           ' at end, terminate string
            process_command                                         ' compare to commands list
            return true
        else
          quit
    
    So... the answer to why you haven't been able to locate a good tutorial probably has to do with the infinite possibilities. I've written about receiving data using FDS and variants in my Nuts & Volts columns many times.

    Again, you need to know what the data coming in looks like before you can deal with it.
    I am not sure if Spin code is going to be able to handle that.
    It can -- 40K baud is no problem. I wrote the code for a professional laser-tag controller that uses 57600 and process a lot of messages. In that case, I created a variant of FDS which provides a larger receive buffer so that I can queue messages and never miss them. In that case I'm using API-1 mode of the XBee which has a specific format.
  • AribaAriba Posts: 2,682
    The Baudrate for MIDI is 31.25 kBaud, and the standard MIDI events contain no strings.
    There may be some strings in vendor specific SysEx data, but then you need to know the exact format.

    Andy

  • AribaAriba Posts: 2,682
    In this post you can find an example for MIDI receiving and decoding. I also use a FullDuplexSerial variaton with a bigger receive buffer, otherwise Spin is a bit too slow for MIDI.
    See the file "midi_expander.spin" in the ZIP: https://forums.parallax.com/discussion/comment/799305/#Comment_799305
  • otherwise Spin is a bit too slow for MIDI.
    What Andy means is that you wouldn't want to use a Spin method to transmit or receive serial -- it's not fast enough and not buffered; you really want both. Most PASM drivers are a variation of FDS.

    Here's the recieve method from Simple_Serial. It works, but has very limited application. That is to say, stick with FDS or one of its variants.
    PUB rx : rxByte | t
    {{ Receive a byte; blocks caller until byte received. }}
    
      if rxOkay
        dira[sin]~                                          ' make rx pin an input
        waitpeq(inverted & |< sin, |< sin, 0)               ' wait for start bit
        t := cnt + bitTime >> 1                             ' sync + 1/2 bit
        repeat 8
          waitcnt(t += bitTime)                             ' wait for middle of bit
          rxByte := ina[sin] << 7 | rxByte >> 1             ' sample bit 
        waitcnt(t + bitTime)                                ' allow for stop bit 
    
        rxByte := (rxByte ^ inverted) & $FF                 ' adjust for mode and strip off high bits
    
  • Thank you both! I thought it might be able to handle strings somehow, but I just don't have the technical expertise to determine how difficult that would be yet. It doesn't help that I haven't studied PASM much. That's where all the exciting things happen with FDS.

    I do have a copy of Extended FDS, but I want to stick with the faster PASM code.

    The device is basically a MIDI controller and a slave device that directly switches guitar pedals on and off that are (slightly) modified to plug right into it. And it also has audio signal path switching. However, it will be upgraded as time permits to have other functions. That's why we build things : )

    I am going to study PASM so I don't have to do so much searching every time I need low level code. These things take time though.

    What I really want to do, is spend my time developing features that no one else has done yet. It looks like the basic code for handling MIDI is available, but the ability to easily modify the patches with the controller it's self, I haven't seen that yet.

    There is a very nice looking object on the OBEX called MidiIn that looks like it will handle all the difficult signal processing that I don't know how to do. I even found a web site that has a good tutorial on using it.
    http://www.tymkrs.com/code/MidiInMe_Demo_1.spin

    I really only need to turn channels off and on, so the signal processing is the hard part on the receive end.

    I had been using my own crude code to send and receive signals. It's fine for sending a few dozen bits, but it's like trying to fight a war with rocks when it comes to sending bytes.

    What I really need FDS for is for sending the full stack of saved patches back a forth between devices. I want to be able to easily back everything up without having to pull memory storage devices out of the units.

    With the code you provided Jon, I should be able to get communication up in less than ten hours of labor : ) Hopefully much less. I am also going to check out your Nuts & Volts columns.

    I'll check out midi_expander as well Ariba!

  • Right now, the only type of messages that I need to send to my receiving end are note on, note off, and key number. Now, THAT information I found quite easily!

    The only reason why this system is a difficult for me, is that it needs to be compatible with commercial devices. Because, I want to be able to plug commercial devices into my system.

    Even sending data the whole stack of patches from one unit to another isn't really difficult. The just have to speak whatever gutter language I cook up... or borrow. But MIDI is a set standard. So I have to get this set up to standards.

    I already have built and partially tested the old hardware that drives signal through the circular 5-pin cable. Even though it's supposed to work with 5 volt electronics, it's easy to adapt the official standard to work with 3.3 volt processors.

    I haven't speced out how what MIDI messages I am going to use. However, it looks relatively easy. I found a good tutorial on Sparkfun
    https://learn.sparkfun.com/tutorials/midi-tutorial/all.

    I think I will save that part for later, though. First I need to get raw data sent and received with FDS.

  • Cluso99Cluso99 Posts: 18,066
    Spin is easier for parts that don't need speed. Reserve PASM for code where speed is required. Don't forget you cannot mix spin and PASM in the same cog though.
  • Ah, I did not know that you couldn't put them in the same cog. No problem yet, though. I am only using three cogs in the control unit so far. And one in the receiver.
  • I found this code posted by AntoineDoinel on another thread:
    https://forums.parallax.com/discussion/128443/newb-looking-to-make-a-midi-controller/p1

    It looks like a good example of how to send MIDI code via FullDuplexSerial.
    CON
      MIDI_OUT_PIN  = 0             'MIDI out pin
      MIDI_IN_PIN   = 1
    
      OPEN_DRAIN    = true          'if using the standard MIDI output circuitry TX must be open drain
      INVERT_TX     = false         'change if required
    
      BASE_OCT      = 0             'can go down to -2 or up
    
    OBJ
      ser : "FullDuplexSerial"
    
    VAR
      long onems
    
    PUB main | n, mode
      mode := 0
      if INVERT_TX
        mode |= 2
      if OPEN_DRAIN
        mode |= 4
      ser.start(MIDI_IN_PIN, MIDI_OUT_PIN, 31250, mode)
      waitcnt(cnt + clkfreq * 3)
      onems := clkfreq / 1000
      repeat
        repeat n from 0 to 60
          send_note(n, 500)
        waitcnt(cnt + clkfreq * 10)
    
    PRI send_note(pitch, duration)
      'send NoteOn message
      ser.tx($90)
      ser.tx((BASE_OCT + 2) * 12 + pitch)
      ser.tx($40)
      'wait for a while
      waitcnt(cnt + duration * onems)
      'send NoteOff message
      ser.tx($80)
      ser.tx((BASE_OCT + 2) * 12 + pitch)
      ser.tx($40)
    
    
  • Robert, most likely all that you need to do can be done in Spin using fullDuplexSerial or one of its offshoots. You won't need to dip into PASM to do what you want with MIDI.

    It is the RxCheck method that is the answer to your question...
    But receiving data is another matter. Do I loop one of the methods to look for incoming data? What do I have to add to the Con section? Inquiring minds want to know.
    Look at the Spin code for fullDuplexSerial to see how the subsidiary methods for Rx and RxTime are built around RxCheck.

    RxCheck pulls a character from the buffer (or returns -1 if nothing there) and does something with it, which might be to move it somewhere else (like a string buffer), or match it against a delimiter like ascii 13 or a comma or decimal point or a letter "M" or "m" (...), or it might take some action like hitting a drum if it receives a certain sequence, or it might test for a digit to build up into a decimal number or hex number or binary number, or it might just discard it, or it might wait until something comes in (Rx), or it might wait for a time and then bail out if nothing comes (RxTime). I draw it out just to emphasize what Jon said about there being infinite possibilities. The message is that it all centers around RxCheck and you don't really need to figure out the PASM behind it unless you really want to dig in.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2018-04-10 21:31
    Andy posted a demo that has a method for handling MIDI messages (I'm going to liberate and modify it for a work project!). As I said earlier, you need to know what the data looks like in order to receive it. Since MIDI is based on a fixed specification, this would be worth studying.
    PUB midiplay  | lg, dt, b, v, st, d1, d2, ch
    
      b := st := d1 := d2 := v := 0
    
      repeat
        b := midi.rx                                  'MIDI byte
        if b > 127 and b < $F0
          st := b                                     'new status byte
    
        ch := st & $0F
        d1~
        d2~
    
        case st & $F0                                 'decode MIDI Event
          $90: d1 := midi.rx                          'Note 
               d2 := midi.rx                          'Velocity
               if d2>0
                 synth.noteOn(d1,ch,d2)               'Note On
                 show(15,ch+1,string(127),-1)
               else
                 synth.noteOff(d1,ch)                 'Note Off if Vel=0
                 show(15,ch+1,string("."),-1)
    
          $80: d1 := midi.rx                          'Note Off 
               d2 := midi.rx                          'Velocity
               synth.noteOff(d1,ch)
               show(15,ch+1,string("."),-1)
               if ch==9
                 show(20,ch+1,string(">"),d1)
    
          $A0: d1 := midi.rx                          'Poly AfterTouch 
               d2 := midi.rx                          '(not supported)
    
          $C0: d1 := midi.rx                          'Prg Change 
               synth.prgChange(d1,ch)
               show(0,ch+1,string("Ch:"),ch+1)
               show(6,ch+1,string("Prg:"),d1+1)
    
          $D0: d1 := midi.rx                          'Mono AfterTouch 
    
          $B0: d1 := midi.rx                          'Controller (nr) 
               d2 := midi.rx                          'value
               if d1==7
                 synth.volContr(d2,ch)                '7=Vol
               if d1==10
                 synth.panContr(d2,ch)                '10=Pan
               if d1==120
                 synth.allOff                         'AllNotes Off
                 show(15,ch+1,string(" "),-1)
    
          $E0: d1 := midi.rx                          'Pitch Bender 
               d2 := midi.rx                          '(not supported!)
    
          $F0: if st==$F0                             'SysEx
                 repeat
                   v := midi.rx  
                 until v==$F7
                 d1~
               elseif st==$F2
                 d1 := midi.rx                        '3 byte
                 d2 := midi.rx
    
    In his case he's driving a synthesizer cog, but you can do anything you want once you've captured the data.
  • I think Dee Snyder would advise me to steer clear of that one... of coarse, it's all subjective. And I can't say that anyone's recreation is better than another's.

    You know, Dee Snyder, defender of Metal. Vocalist for Twisted Sister.

    Hey, I'm a guitar play. It's my nature.

    Anyway, I need MIDI compatibility to be able to control modelers and make my looper compatible with other people's MIDI controller. People think of MIDI as being just for making synth music, but it is actually used for controlling all sorts of musical electronics. Basically, it allows me to make custom guitar effects loops that I can switch with one button. No tap dancing. Just, making The Metal!

    However, if I get time, I WILL make synthesizers METAL. Trent Reznor did it on Broken. But, I digress. For now, I just need to get this thing up an running.
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-11 11:59
    Ok, back to seriousness.

    This is, if I am not insane, the very codes that will need to be sent to FullDuplexSerial on the send side:

    $nc, $kk, $vv
    Where:
    $ indicates hexadecimal in Spin code. Other languages might use 0x or whatever....
    n is the command (note on ($90) or off($80))
    c is one of channels 1 thru 16, with each channel being a different device. Since this is all in hex, off on channel ten would be $9A
    But I am assuming for now that my device will be on channel zero.
    kk is the key number (0 to 127, where middle C is key number 60). So transmit something from $00 to $7F. Each key number represents either a relay or a stomp box that will be switched. My rig probably won't have that many devices to switch, but I don't mind having so much breathing room.
    vv is the striking velocity (0 to 127) This has no use in binary switching. So just transmit a fixed value of $40.


    So, a message for switching on stomp boxes two and four would look like this:

    $90, $02, $40
    $90, $04, $40


    It's overkill to use MIDI like this, but again, this is all about compatibility. And, it should be very simple to program all of the necessary codes into the controller I have built. The user need not ever see them, they are just sent as needed. In the GUI, he/she enters a 1 or a 0 for what they want on or off, and the machine sends whatever it needs to send. Actually, I think this is going to be a necessity for preventing insanity.

    Anyone would prefer having to enter "10010000" for a patch that switches eight devices on channel zero to this:
    $80, $00, $40, $90, $01, $40, $80, $02, $40, $90, $03, $40, $80, $04, $40, $80, $05, $40, $80, $06, $40, $80, $07, $40, $80, $08, $40, $80, $09, $40

    The latter is easy to generate via code.

    I don't think it would be necessary to send a code for every "note", though. Just the ones I want the hub to switch on. So:
    $90, $01, $40, $90, $03, $40 The hub turn everything off that it wasn't just told to turn on.

    I already have an patch editor coded in Spin, so that should be a good starting point for this new system.

    The interesting part for editing and displaying the info on the patches, will be figuring out what sort of management is going to be needed for other people's devices. I plan on using this to switch a V-Amp3, probably a V-Amp2, and also a HD500x.

    And, it should be as flexible as possible for other uses. So, that will be a fun challenge.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2018-04-11 15:03
    In the MIDI protocol, channels 1..16 are represented by the values 0..15 ($0..$F) in the low nibble of the status byte.

    I'm a big fan of writing small, atomic methods that are easy to integrate -- even with each other (see our Note On with velocity of 0 calls Note Off).
    pub note_on(ch, note, velocity)
    
      if (velocity == 0)
        note_off(ch, note)
        return
    
      midi.tx($90 | ch)
      midi.tx(note)
      midi.tx(velocity)
    
    
    pub note_off(ch, note)
    
      midi.tx($80 | ch)
      midi.tx(note)
      midi.tx($00)
    
    
    pub controller(ch, ctrlnum, value)
    
      midi.tx($B0 | ch)
      midi.tx(ctrlnum)
      midi.tx(value)
    
    
    pub patch_change(ch, patch)
    
      midi.tx($C0 | ch)
      midi.tx(patch)
    
    These methods assume that the parameters passed are well-behaved. For example, if you sent a channel value of 16 to any of these methods, you'd end up changing the command.

    Things become interesting if you choose to support "running status." To do that you'd have to save the last status byte and filter it from subsequent transmissions.
    pub note_on(ch, note, velocity) : sb
    
      sb := 90 | ch
      if (sb <> laststatus)
        midi.tx(sb)
        laststatus := sb
        
      midi.tx(note)
      midi.tx(velocity)
    
    
    pub note_off(ch, note) : sb
    
      sb := 80 | ch
      if (sb <> laststatus)
        midi.tx(sb)
        laststatus := sb
    
      midi.tx(note)
      midi.tx($00)
    
    The final complication -- as I've been reading -- is the Explicit Note Off message where there is no velocity byte. In the MIDI receiver I'm working on I have a setting that allows for this (needs to be tested).
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-12 10:32
    JonnyMac wrote: »
    I'm a big fan of writing small, atomic methods that are easy to integrate -- even with each other (see our Note On with velocity of 0 calls Note Off).


    Yeah, I'm trying to do that. It seems like I cannot get that in the first draft; it tends to be the third revision that is configured in nice neat methods.

    My more complicated stuff looks like this:
    PUB TextScan  | x , y  , u  , bill , steve
    
      dira[16] := %0
      dira[17..21] := %11111
    
      Stevatron      'Fetch presets
      
      S := 0      'Keeps track of what space you are on.
      y := 0     'Keeps track of what line you are on.     
      x:= 0      'Keeps track of what character you are tweeking.
      u := 0   'Keeps track of what space on that line you are on.
      j := 4    'Stops Interpreter from making those damn arrows.
    
      
      bill := 0  'Bill keeps track of cycling between different sections of the ASCII 2 characters
    
      x := 0
          
        repeat 10
          if o[x] == 0
             o[x] := 32
         x += 1
    
       x := 0
    
      lcd.bigfont((o[x]),S,y,true)      'I think this just shows you where the cursor is.
      waitcnt(clkfreq + cnt)  
    
       go := 1
    
     
    repeat
      outa[17..20] := %0000                         'Button 0: Previous character
      if ina[16] == 1 and y == 0                  'Disallow for lines 1 thru 3
           if ina[16] == 1 and o[x] > 0           'Disallow for too low
             if o[x] == 32 and bill == 0      
                  o[x] := 123
    
             if o[x] == 32 and bill > 0
                  o[x] := steve
                  
             if bill > 0 and o[x] == 32
               o[x] := o[x-1]
               o[x] += 1
    
            o[x] -= 1 
            
             steve := o[x]
    
            lcd.bigfont((o[x]),S,y,true)     'Display the new character
    
            waitcnt(clkfreq/5 + cnt) 
    
      if ina[16] == 1
        If y == 1 or y == 2          'Disallow for line 0 or 3          
          if o[x] == 0
            o[x] := 1 
            lcd.bigfont((49 + u),S,y,true) 
            waitpeq(%0, |< 16, 0)
            waitcnt(clkfreq/10 + cnt)
                  
          if ina[16] == 1 and o[x] == 1          
            o[x] := 0
            lcd.bigfont(32,S,y,true) 
            waitpeq(%0, |< 0, 0)
            waitcnt(clkfreq/10 + cnt)
        Multiplexer
    
      if ina[16] == 1
        If y == 3                    'Allow for line 3
          if o[x] > 0         
            o[x] -= 1
            GenWriteLine3T (x, S, y) 
            waitpeq(%0, |< 16, 0)
            waitcnt(clkfreq/10 + cnt)            
    
        Multiplexer
              
      outa[17..20] := %1000                           'Button 1:  Next character
      if ina[16] == 1 and y == 0                       'Disallow for lines 1 thru 3 
    
         if o[x] == 32 and bill == 0
                  o[x] := 96
    
         if bill > 0 and o[x] == 32
               o[x] := o[x-1]
            o[x] -= 1 
            
         o[x] += 1 
         steve := o[x]
         lcd.bigfont((o[x]),S,y,true) 
          waitcnt(clkfreq/5 + cnt)
    
      if ina[16] == 1
        If y == 1 or y == 2         'Disallow for line 0 or 3
          if o[x] == 0
            o[x] := 1 
            lcd.bigfont((49 + u),S,y,true) 
            waitpeq(%0, |< 16, 0)
            waitcnt(clkfreq/10 + cnt)
                  
          if ina[16] == 1 and o[x] == 1          
            o[x] := 0
            lcd.bigfont(32,S,y,true) 
            waitpeq(%0, |< 16, 0)
            waitcnt(clkfreq/10 + cnt)
    
        Multiplexer
      
      if ina[16] == 1
        If y == 3                       'Allow for line 3
          if o[x] < 26       
              o[x] += 1
              GenWriteLine3T (x, S, y) 
              waitpeq(%0, |< 16, 0)
              waitcnt(clkfreq/10 + cnt)    
                    
    
        Multiplexer
    
      outa[17..20] := %0100                         'Button 2:  Next space
      if ina[16] == 1 and y == 0             
         if S < 9
             lcd.bigfont((o[x]),S,y,false) 
             S += 1 
             x += 1
             u += 1
             GenWrite0T(x,S,y) 
             waitcnt(clkfreq/4 + cnt)
    
      if ina[16] == 1
        if y == 1 or y == 2
         if S < 7
             GenWriteF(x,S,y,u)
             S += 1 
             x += 1
             u += 1
             GenWriteT(x,S,y,u)                                         
             waitcnt(clkfreq/4 + cnt)
         
    
    
      if ina[16] == 1
        if y == 3
         if S < 7
             GenWriteLine3F (x, S, y) 
             S += 1 
             x += 1
             u += 1
             GenWriteLine3T (x, S, y)                                        
             waitcnt(clkfreq/4 + cnt)         
    
      outa[17..20] := %1100                           'Button 3:  Previous space
      if ina[16] == 1 and y == 0
         if S > 0
             lcd.bigfont((o[x]),S,y,false) 
             S -= 1 
             x -= 1
             u -= 1
            GenWrite0T(x,S,y)
            waitcnt(clkfreq/4 + cnt)
    
      if ina[16] == 1
        if y == 1 or y == 2 
         if S > 0
             GenWriteF(x,S,y,u)
             S -= 1 
              x -= 1
              u -= 1
              GenWriteT(x,S,y,u)  
            waitcnt(clkfreq/4 + cnt)
    
      if ina[16] == 1
        if y == 3
         if S > 0
             GenWriteLine3F (x, S, y) 
             S -= 1 
             x -= 1
             u -= 1
             GenWriteLine3T (x, S, y)                                        
             waitcnt(clkfreq/4 + cnt)        
             
             
      outa[17..20] := %0010                               'Button 4:  Edit text line 0
        if ina[16] == 1
          if y == 1 or y == 2
            GenWriteF(x,S,y,u) 
            S := 0 
            x := 0  
            y := 0  
            u := 0
            GenWrite0T(x,S,y) 
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)
    
        if ina[16] == 1 and y == 3
            GenWriteLine3F (x, S, y) 
            S := 0 
            x := 0  
            y := 0  
            u := 0
            GenWrite0T(x,S,y) 
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)
              
           
       if ina[16] == 1 and S < 9
             lcd.bigfont((o[x]),S,y,false) 
             S += 1 
              x += 1
              u += 1
              GenWrite0T(x,S,y)  
              waitcnt(clkfreq/8 + cnt)    
    
    
      outa[17..20] := %1010                               'Button 5:  Edit data line 1 
          if ina[16] == 1 and y < 1
              GenWrite0F(x,S,y)
              S := 0
              y := 1 
              x := 20 
              u := 0
              GenWriteT(x,S,y,u)
              waitpeq(%0, |< 16, 0) 
              waitcnt(clkfreq/4 + cnt)
    
          if ina[16] == 1 and y == 2
              GenWriteF(x,S,y,u)
              S := 0
              y := 1 
              x := 20  
              u := 0
              GenWriteT(x,S,y,u)
              waitpeq(%0, |< 16, 0) 
              waitcnt(clkfreq/4 + cnt)
    
        if ina[16] == 1 and y == 3
            GenWriteLine3F (x, S, y) 
              S := 0
              y := 1 
              x := 20  
              u := 0
            GenWriteT(x,S,y,u) 
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)
                                          
               
       if ina[16] == 1 and y == 1
          waitcnt(clkfreq/4 + cnt)
           if ina[16] == 1 and S < 7
              GenWriteF(x,S,y,u)
              S += 1 
              x += 1
              u += 1
              GenWriteT(x,S,y,u)  
              waitcnt(clkfreq/8 + cnt)   
        
    
      outa[17..20] := %0110                               'Button 6:  Edit data line 2
        if ina[16] == 1 and y < 2
    
             if y == 1
               GenWriteF(x,S,y,u)
    
             if y == 0
               GenWrite0F(x,S,y)
                   
            S := 0
            y := 2 
            x := 28  
            u := 0
            GenWriteT(x,S,y,u)  
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)  
    
        if ina[16] == 1 and y == 3
            GenWriteLine3F (x, S, y) 
            S := 0
            y := 2 
            x := 28  
            u := 0
            GenWriteT(x,S,y,u) 
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)  
    
       if ina[16] == 1 and y == 2
          waitcnt(clkfreq/4 + cnt)
           if ina[16] == 1 and S < 7
             GenWriteF(x,S,y,u)
             S += 1 
             x += 1
             u += 1
             GenWriteT(x,S,y,u)  
             waitcnt(clkfreq/8 + cnt)     
    
      outa[17..20] := %1110                               'Button 7:  Edit data line 3
        if ina[16] == 1 and y < 3
             If y == 0
               GenWrite0F(x,S,y) 
    
             If y == 1 or y == 2
               GenWriteF(x,S,y,u) 
           
            S := 0
            y := 3 
            x := 36  
            u := 0
            GenWriteLine3T (x, S, y) 
            waitpeq(%0, |< 16, 0) 
            waitcnt(clkfreq/4 + cnt)  
             
       if ina[16] == 1 and y == 3
          waitcnt(clkfreq/4 + cnt)
           if ina[16] == 1 and S < 7
             GenWriteLine3F (x, S, y)
             S += 1 
             x += 1
             u += 1
             GenWriteLine3T (x, S, y)  
             waitcnt(clkfreq/8 + cnt)     
    
      outa[17..20] := %0011                               'Scan Button 12
      if ina[16] == 1
            waitpeq(%0, |< 16, 0)
            Oloader
            Stevatron
            TextDisplayO
            h := 0                  'Tells the Multiplexer method to go back to using "e", instead of "o"  
            Multiplexer
            EraseArrow
            Scan
    
      outa[17..20] := %1111                                'Scan Button 15
      if ina[16] == 1
            waitpeq(%0, |< 16, 0) 
            Galvatron
            
              SaveData                 'Save all the new presets to EEPROM
              SaveNames
    
            if y == 0
               GenWrite0F(x,S,y) 
    
            if y == 1 or y == 2
               GenWriteF(x,S,y,u)
    
            if y == 3
               GenWriteLine3F (x, S, y)            
            waitcnt(clkfreq/2 + cnt)
            h := 0                        'Tells the Multiplexer method to go back to using "e", instead of "o"
            EraseArrow 
            Scan
    
    
      outa[17..20] := %1011                               'Scan Button 13
      if (ina[16] == 1) and (y == 0)
    
            if bill == 0 
                  o[x] := 77      'M
                  GenWrite0T(x,S,y)   
                  bill := 1
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
    
            if ina[16] == 1 and bill == 1
                  o[x] := 109     'm
                  GenWrite0T(x,S,y)   
                  bill := 2
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
    
            if ina[16] == 1 and bill == 2 
                if o[x] > 64
                  o[x] := 49      '1
                  GenWrite0T(x,S,y)  
                  bill := 3
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
                  
            if ina[16] == 1 and bill == 3 
                  o[x] := 31       'some weird symbol like a transistor
                  GenWrite0T(x,S,y)
                  bill := 4
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
                        
    
            if ina[16] == 1 and bill == 4 
                  o[x] := 150      'a weird u
                  GenWrite0T(x,S,y)  
                  bill := 5
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
    
            if ina[16] == 1 and bill == 5 
                  o[x] := 250
                  GenWrite0T(x,S,y)  
                  bill := 6
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
                              
            if ina[16] == 1 and bill == 6 
                  o[x] := 350
                  GenWrite0T(x,S,y)  
                  bill := 7
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)
    
            if ina[16] == 1 and bill == 7 
                  o[x] := 450
                  GenWrite0T(x,S,y)  
                  bill := 0
                  waitpeq(%0, |< 0, 0)  
                  waitcnt(clkfreq/2 + cnt)                                                             
    
      outa[17..20] := %0111                    'Scan Button 14
      if (ina[16] == 1) and (y == 0)              'The second part of this line prevents malfunction when the button is pressed by accident. j cannot be more than 2.
        o[x] := 32
        GenWrite0T(x,S,y)
        bill := 0
        waitpeq(%0, |< 0, 0) 
        waitcnt(clkfreq/2 + cnt)
      go := 1
    
    

    That is the editor that is used to enter/change the name of the patch, the binary settings (because most of the data is just for switching things on and off), changing the settings that are integers (such as which bank number I want the modeler on), and it also sends data to an LCD screen driver. This version sends data to HugeLcd.Driver. So, tricks need to be employed so that the proper ASCII2 code is sent.

    Nothing conceptually difficult has to happen on this end to make it work. Ergonomics, on the other hand, are difficult to design. How much information should be displayed on the screen? It's one of those large blue ones you could get from Sparkfun:
    https://www.sparkfun.com/products/retired/8799
    No longer available, btw :(

    One of the nice things about the driver I am using for this unit, is the ability to display large or small characters. The large characters are easy to read from far away, but you only get five lines with those. Sixteen lines of small characters will fit, though.

    There is no way the actual data that would be sent would all fit on the screen. So, some kind of shorthand will be necessary. This shouldn't be a problem. The software already does a lot of things to make the ASCII2 codes that are needed for the display. Making MIDI codes to sent will just be a matter of writing a bit more code.







  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-12 11:12
    Right now, this is how the information is displayed on the send unit's LCD screen:

    Metallica <--- this is the name line. It can be just about any ASCII2 code. Up to ten characters, as this is the large text line.
    1 4 8 <--- binary data line one. It's actually storing either a one or a zero, and displaying various numerals for clarity.
    2 <--- binary data line two. Works just like binary data line one.
    BAC <--- this is the integer line. It displays letters to save space. Each space (there are eight total) can be from A to Y.


    The current version uses large characters to display everything. I think I am going to have to go to small character for everything but the name line. That way, the binary data can be displayed on one line (it will fit 20 small characters on a line), and I will have eleven others for the MIDI settings.

    But, how does the MIDI data need to be displayed?

    Perhaps:
    Metallica <--- pretend these are really big characters.
    A D H J P <--- singular binary data line. I would use letters so I can stick to single small characters. This is for my hub.
    Vamp3 | 91,03,40 <--- A short name, and a MIDI code per line.
    HD500x | 92,14,40
    Keyboard| 93,08,40 <--- Or you just keep entering codes. Somehow the machine will need to be told what type of data is in each slot (2 per line)
    93,15,50 |93,1E,60 <--- I have no idea what actual codes I will be sending, but it might be a lot. So I think it needs to be an unlimited number
    of messages per patch. This should be doable. I use 64K EEPROM's, and the whole upper half is used for storing
    variables. Start bits and end bits (or bytes) would be needed.
  • There is plenty of room to display a lot of information if I mostly use small type. This unit has about a five inch diagonal viewing area. There is probably a reasonable way to make it scroll or page down, but I haven't thought that out yet. It is NOT a touch screen.
    1645 x 1234 - 1M
  • Cluso99 wrote: »
    Spin is easier for parts that don't need speed. Reserve PASM for code where speed is required. Don't forget you cannot mix spin and PASM in the same cog though.

    Unless you use fastspin :) (in which case your spin gets turned into PASM and you can write inline PASM to go with it).
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-13 09:25
    Argggg.... I am going to have to annotate FullDuplexSerial myself, and reverse engineer it.

    I think I need to remind people that not everyone on the forums is a professional software engineer.

    And yes, it is worthwhile to take the time to see how things work.

    Having said that, we all only have limited time to get things done. So, tutorials are a good thing.
  • kwinnkwinn Posts: 8,697
    Argggg.... I am going to have to annotate FullDuplexSerial myself, and reverse engineer it.

    I think I need to remind people that not everyone on the forums is a professional software engineer.

    And yes, it is worthwhile to take the time to see how things work.

    Having said that, we all only have limited time to get things done. So, tutorials are a good thing.

    Why? FullDuplexSerial works well and the Spin routines are reasonably straight forward. Pretty much everything you need to send and receive 8 bit bytes in multiple formats is there. Study the Spin "Pub" routines and add some comments to them and you should have a pretty good handle on serial data.

    rx/tx receive/send 8 bits (1 byte) of data
    str uses tx to send a specified number of bytes of data
    hex uses tx to send a byte as two hexadecimal characters (0 - 9, A, B, C, D, E, F)
    etc. etc. etc.

    You can add Spin "Pub" routines to format the data in other ways. For instance I have added routines to receive strings of data terminated by a specified character/byte. It is mainly used to get a number terminated by a "cr", but since I can specify the terminating byte it can be used for other things.
  • kwinn wrote: »

    Why? FullDuplexSerial works well and the Spin routines are reasonably straight forward. Pretty much everything you need to send and receive 8 bit bytes in multiple formats is there. Study the Spin "Pub" routines and add some comments to them and you should have a pretty good handle on serial data.

    That's what I am doing now. I didn't really study the operators in depth when I did the front end of my one big Propeller project. So, now I am look at lines like this and scratching my head:

    repeat while (rxbyte := rxcheck) < 0

    You probably recognize that from the rx method of FDS. It's not setting rxbyte equal to rxcheck, is it? What exactly is it doing? I have been looking through the manual to find what other syntax := is used for.

    I had a decent front end, and now I am trying to bring the back end up to standards. This has to be done in less than 80 hours. Or I have to cancel the upgrade.

  • I think I understand the check_message method that Jon posted way up there. I am going to give that a try.
  • kwinnkwinn Posts: 8,697
    The "(rxbyte := rxcheck)" is setting rxbyte to the value returned by the "rxcheck" method.

    So the full statement:
    repeat until (rxbyte := rxcheck) => 0 or (cnt - t) / (clkfreq / 1000) > ms
    Translates to:
    Repeat until
    (the value returned by the rxcheck method is equal or greater than 0)
    or
    ((cnt - t) / (clkfreq / 1000)) is greater than ms

    t is the value of the cnt register before entering the repeat loop
    (clkfreq / 1000) is the number of clock cycles in one ms
    (cnt - t) is the number of cycles that have passed since the repeat loop was entered

    So basically repeat until a byte is returned or the specified no of milliseconds have passed.
  • Tracy AllenTracy Allen Posts: 6,656
    edited 2018-04-14 22:03
    Robert,

    Yes, Rx starts off,
    repeat while (rxbyte := RxCheck) <0
    
    It is among the Propeller's assignment operators, which have the following property, from the Prop manual 1v2 (table on pages 144-145, and following text):
    Those that are assignment operators, however, write their result to either the variable they operated on (unary), or to the variable to their immediate left (binary), in addition to providing the result for use by the rest of the expression. .
    Study the table of operators charfully!

    So the statement is doing two things, assigning whatever is returned by RxCheck to the variable rxbyte, and then checking to see if rxbyte is less than zero. RxCheck returns a value -1 if nothing is in the serial queue. Consequently the Rx method simply sits there until real ascii character with a value of 0 to 255 appears is received. If something is received, the application program will usually want to do something with it, good, there it is in rxbyte, which is returned to the calling program as the result of Rx to use as it sees fit.

    The following abbreviated form,
    repeat while RxCheck <0
    
    would also stick in the repeat until something is received, and that might be fine if all you want to do, say, is to detect if a user has press any key. But it won't do you any good if you need to do something with the received value, because you can't use RxCheck again to capture the same character. The next RxCheck points to a new position in the queue, another character received or nothing.

    The example that Kwinn mentioned is for RxTime, which adds a timeout to the process, which is useful, say, if you want to give a user x seconds to press a key, but move on if not. If it does receive something before the timeout, it is passed on to the caller, or -1 if nothing or timeout.

    In application code you will often see something like this, with an if instead of a repeat
    if (rxbyte := RxCheck) > -1
       ' ...
    
    That would usually part of a larger loop that occasionally checks to see if there is a character ready in the buffer. The calling program can pick up a command or whatever when one happens to arrive, but otherwise manage other tasks.
  • So the statement is doing two things, assigning whatever is returned by RxCheck to the variable rxbyte, and then checking to see if rxbyte is less than zero. RxCheck returns a value -1 if nothing is in the serial queue. Consequently the Rx method simply sits there until real ascii character with a value of 0 to 255 appears is received. If something is received, the application program will usually want to do something with it, good, there it is in rxbyte, which is returned to the calling program as the result of Rx to use as it sees fit.
    Just when I think I might know Tracy, I find out I need to know more. It looks like you can loop RxTime to capture the whole message. Jon's code above does that. But I don't think RxTime was designed for that. Otherwise, what would Rx be there for?

    I am probably going to need to be sending strings. So, I think I am going to use RxCheck to detect the head of the message, and then loop Rx to capture the rest.

    I've been trying to get the send side partially done, but my old spaghetti code is just about clogging up the Propeller chip.

    This locks the system up:
    Pub Multiplexer  |  x               
    
        Cyclotron
    
          if v > 0
            v -= 1
    
          x := 0
    
          if h == 0
            FDS.str($7E)
            repeat 19                     
                 FDS.Tx(e[x])   'maybe FDS.Tx(e[x++]) would work better
                 x += 1           'and then this line would not be needed
    
    
    return
    
    I don't know why exactly it does this, but every time I loop a call like the above, the Prop locks up. I should have gotten on adopting a respectable com protocol earlier, and not waited until I needed it.

    I am going to try to use this instead:
    Pub Multiplexer  |  x               
    
        Cyclotron
    
          if v > 0
            v -= 1
    
            FDS.str($7E)  'or is hex the correct method for this?
            FDS.str(@e)
    
    
    
    
    I probably should have been sending strings like that all along. But, it wasn't easy to find out how to send an array as a string like that. I replaced this display code:
      repeat 10
        ifnot (o[x]) == 0
          lcd.bigfont((o[x]),y,0,false)
        else
          lcd.bigfont(32,y,0,false)
    
    with this much neater code that seems to be working quite well:
      lcd.bigstring(@o,0,0,false) 
    
    I am thinking
        FDS.str(@e)
    
    Is much superior to
        repeat 19                     
            FDS.Tx(e[x])
            x += 1
    
    This is my homebrew code for sending data:
    Pub Multiplexer  |  x  , y , w , q[5]              
    
        Cyclotron
    
          if v > 0
            v -= 1
    
          dira[12..13]  := %11
    
          q[0] := 0
          q[1] := 1
          q[2] := 0
          q[3] := 1  
    
          x := 0
    
          outa[13] := %1                     'Hey dude 
          waitcnt(clkfreq/40000 + cnt)                      
          repeat 4                          'Yo eat this  
              outa[12] := q[x]
              x += 1
              waitcnt(clkfreq/51000 + cnt)
          x := 0
    
    
            outa[13] := %0    
            waitcnt(clkfreq/10000 + cnt)
          
            repeat 2
                outa[13] := %1                      'Hey dude
                waitcnt(clkfreq/40000 + cnt) 
              repeat 8                      
                 outa[12] := e[x]
                 x += 1
                 waitcnt(clkfreq/51000 + cnt)
               
              outa[13] := %0    
              waitcnt(clkfreq/10000 + cnt)
    
    
              waitcnt(clkfreq/10000 + cnt)    
    
          repeat 3
             y := 0
             if h == 0
               w := e[x]
    
    
             outa[13] := %1                      'Hey dude
             waitcnt(clkfreq/40000 + cnt)
    
               repeat w
                 if y > 8
                   y := 0
                   outa[12] := %0    
                   waitcnt(clkfreq/10000 + cnt)
                   outa[12] := %1
                   waitcnt(clkfreq/40000 + cnt)
    
                 y += 1                   
                 outa[12] := %1             
                 waitcnt(clkfreq/34500 + cnt)   'Longer pause, because there is little code executing.
    
              outa[12] := %0
              x += 1
              outa[13] := %0    
              waitcnt(clkfreq/10000 + cnt)         
                   
    return
    
    
    The only good things about the above is that doesn't take a lot of resources, and it is easy to modify. It is incapable of sending bytes in any convenient format. However, I DIDN'T NEED TO SEND BYTES. So, it was fine in the past. Basically, the device only flipped relays on and off.

    The problem I face now, is modifying it so that it can use MIDI code.

    This is easy on the receiving end, because that side has 6600 longs free.

    It's the sending side that I am worried about, because it keeps locking up whenever I try to include a complicated upgrade. I am going to have to simplify it's code, and maybe eliminate some features that I won't be using much. Or I will have to go to a dual boot system.

    Is it locking up because it only had 3600 longs free? Maybe it's due to my liberal use of global variables?

    I am going to keep trying to make the software send some kind code with FullDuplexSerial. I will make an entire custom test object if I have to, but I want to keep under my 80 hour time budget for this upgrade.


  • AribaAriba Posts: 2,682
    ...
    I am thinking
        FDS.str(@e)
    
    Is much superior to
        repeat 19                     
            FDS.Tx(e[x])
            x += 1
    
    ...

    FDS.str(@e) sends the bytes from the e[] array until a zero-byte is reached, while your repeat code sends the first 19 bytes in e[] independent of the value of the bytes (also zeroes).
            FDS.str($7E)  'or is hex the correct method for this?
            FDS.str(@e)
    
    FDS.str($7E) sends the bytes from address $007E in hubmemory, i don't think this is what you want.
    FDS.hex($7E,2) sends 2 characters '7' and 'E' as ASCII codes, maybe you want that, FDS.tx($7E) sends a single byte with value $7E, I think this is what you need for MIDI messages.

    Andy
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-15 09:29
    Thank you Andy, you continue to illuminate!

    I have no idea where the addresses of the variables are (yet), so FDS.str($7E) isn't at all right. Maybe if I knew how to aim....

    I thought Tx would only handle one character for some reason. I guess I just haven't gotten my head wrapped around hexadecimal yet. I'm working on it.

    I was trying to send bytes in hexadecimal (which is what MIDI is built on), so FDS.tx($7E) is what I probably need for sending MIDI.

    I still want to get some variation on this working though:
        repeat 19                     
            FDS.Tx(e[x])
            x += 1
    
    ...for the simple fact that it is so convenient to just be able to send a one or a zero to tell the receiving end what to do with the relays.

  • Well, for a brief shining moment, FDS.tx($7E) was sending properly! And then it locked up, and I couldn't get the send end to work again.

    I might just have to build a whole new interface around FullDuplex serial.
  • ErNaErNa Posts: 1,738
    Long time ago, when there was a president leading the US, I created a CMAP to document Full Duplex Serial and some modifications, I made, so here is a diagram, that could provide some help in understanding what is going on and what modifications can be done. cmapspublic3.ihmc.us/rid=1SCYFXX2T-1FT6NM1-1KH0/Full_Duplex_Serial.cmap
    If you want to have access to the CMAP to work with it, use CMap Tools.
Sign In or Register to comment.