Shop OBEX P1 Docs P2 Docs Learn Events
Receiving Strings and Writing them to an Array, Question ... — Parallax Forums

Receiving Strings and Writing them to an Array, Question ...

JMLStamp2pJMLStamp2p Posts: 259
edited 2011-12-16 07:41 in Propeller 1
I want to receive Two ASCII characters and Save them to an Array called rxStr_array. After Two characters are received I want to Transmit those characters out the serial port ...
Can someone please let me know if the following code is the correct way of accomplishing this task. If it is not please explain what I am doing wrong.
Thank you,
John Michael

PUB rxStr(num_of_chars, strPtr) | char, index                  'strPtr holds the 16 bit location of the rxStr_array.
                                                                                    'num_of_chars is the amount of characters we want to receive.
    index:=0                                                                   'Set the index pointer for rxStr_array to location "0".

    repeat                                            'Repeat the following ...
      char := serial.rx                             'The Variblle char is set to the Character that is received into the serial port.

      if num_of_chars == 2                    'Check to see if we have received 2 ASCII characters,
                                                         'If not Loop again until we have.
         byte[strPtr][index]:=char             'Write the character to the array at the index position.
         index++                                     'increment the index or position that will hold the next character received.

     if num_of_chars--==1                                    'If we have received the Total number of characters ...
        Transmit_Timer_Values(@rxStr_array)        'Transmit the characters that is in the rxStr_array.

    serial.rxflush                                                  'Flush the input Buffer.
    num_of_chars:=2                                           'Set the Total characters to receive back to "2".

«1

Comments

  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-05 12:47
    What about this??
    PUB rxStr(num_of_chars, strPtr) | index
        index:=0
        repeat num_of_chars
          byte[strPtr][index++] := serial.rx
    
        Transmit_Timer_values( strPtr )
    

    It pretty much depends on what your goals are to give you the best possible code. For example in your implementation
    Transmit_Timer_values( @rxStr_array )
    you used rxStr_array directly ... so, why do you pass it as parameter to the function? Why do you pass num_of_chars if your code can't properly handle different values?

    You see the difference?
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-06 04:06
    Yes I see what you mean, what I am trying to do is Parse incoming Strings and test a condition "go" or "stop" by turning on LED_0 or LED_7. I have the Propeller receiving and Turning on LED_0 now, so at least I know that I have it wired right :>)
    My code still cannot tell the difference between Go and stop though. Code is on another computer, I will post it later this morning to get your advise.

    Thank you,
    John michael
  • John MichaelJohn Michael Posts: 37
    edited 2011-12-06 05:42
    "What I am trying to do is Parse incoming Strings and test a condition "go" or "stop" by turning on LED_0 or LED_7"
    The Propeller is receiving the Byte but turns on LED_0 no matter what Character I send. Need some Help to accomplish this task, I don't really understand how to terminate the string correctly and need
    to see a small Bit of example code so I can better understand it. My code so far is:
    PUB Receive| Value, ptr
                                             
      serial.rxflush                               'Flush Buffer
      ptr:=0                                         'Set index pointer of the dataIn Array to location 0.
      Value:= serial.rxStr(@datain)       'Points to the address of the dataIn array and assign "Value" to the byte that was received by "serial.rxStr".                 
      byte[datain][index]:=Value           'Writes the first Byte Received to the dataIn array pointed to by "ptr". Is this redundant of the Previous Line?
      ptr++                                         'Increments the dataIn Arrays Index Pointer, so the next byte received will go to the next position in the array.
      
      repeat                                        'Check to see what Character was received.
        if dataIn[0] := ("A")
           LED_0
           
        if dataIn[0] := ("B")
           LED_7     
                   
    PUB LED_0
     
          !outa[0]
          waitcnt(clkfreq+cnt)
          !outa[0]
          waitcnt(clkfreq+cnt)
          Receive
          
    PUB LED_7
        
          !outa[7]
          waitcnt(clkfreq+cnt)
          !outa[7] 
          waitcnt(clkfreq+cnt)
      
          Receive
          
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-06 06:04
    Hi John ... your code ... well, have a look:

    if dataIn[0] := ("A")
    This is an assignment and meanwhile it is a bug ;o)
    if dataIn[0] == "A"
    would be the correction for that.

    BUT ... why ??? Use parameters to have some more flexible code and use math
    PUB LED( whichOne )
          !outa[ whichOne ]
          waitcnt(clkfreq+cnt)
          !outa[ whichOne ]
          waitcnt(clkfreq+cnt)
    

    Then you can remove the whole if by
      LED( dataIn[0]-"A" )
    
    of course this needs the LEDs to be in a row. If you want to be flexible you can setup a lookup-table to get the right pin number from an array.

    Depending on the reliability of the input-data you can have some safety checks before. For example:
    if dataIn[0]=>"A" and dataIn[0]=<"B"
    

    In the LED_0 and LED_7 functions you call Receive. This is called a recursion because you call LED_x from Receive and then Receive from LED_x. This will eat up your stack-space. When the end is reached strange things will start to happen as you overwrite code then. From functions you should somewhen return.

    Further explanations:
    if you stored an "A" in dataIn[0], the calculation dataIn[0]-"A" will simply give you 0 (zero).
    if you stored a "B" in dataIn[0], the calculation dataIn[0]-"A" will simply give you 1 .......
    There is no need to have 2 if statements (and 2 LED-functions) if the rest of the code can deal with these calculated numbers.
  • John MichaelJohn Michael Posts: 37
    edited 2011-12-06 09:54
    MagIO2, thanks for your advice ...
    I am just learning Spin so I am still trying to understand how to write code :>) When I use the rxStr function to receive a "byte" in the serial port via Extended_FDSerial, is the byte received already written to the dataIn array and the pointer incremented? Am I trying to insert un-nessessary code in my function?
    Do I need to just Terminate the string in my code after a certain number of characters are received? Not sure how to write my receive code to test for a certain number of characters and terminate the string correctly.

    Extended_FDSerial statement:
    Accepts a string of characters - up to 15 - to be passed by reference
    String acceptance terminates with a carriage return or the defined delimiter character.

    The following is my complete code so far "as small as it is", the Propeller sends the correct commands to the TeLit Cellular engine and Transmits the message "System is Initiated" to my Cell phone. I want to return either "Go" or "Stop" back to the Propeller and have it turn on LED-0 or LED-7 on my Professional Development Board. The PUB INITIATE function is working correctly and sending the message, just having a Bit of trouble understanding the correct procedures for the receive function. If you could Help with this it would be Greatly apreciated.
    CON
       _clkmode = xtal1 + pll16x
       _clkfreq = 5_000_000
    
    OBJ
       serial: "Extended_FDSerial"
    
    VAR
       long Stack[400]
       Byte datain
    
    PUB INITIATE 
    
       dira[0..7]~~                  
       serial.stop
       serial.RxFlush
       serial.start(26,25,%0000,115200)
    
       serial.str(String("AT+CMGD=1,4",13))
       waitcnt(clkfreq+cnt)
       
       serial.str(String("AT+CMGF=1",13))
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT#SMSMODE=0",13))
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CPMS=ME",13))
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CNMI=1,1,0,0,0",13))
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CMGS=",34,"+12052604026",34,13))        'Install your number
       waitcnt(clkfreq+cnt)
    
       serial.str(String("System Is Initiated"))
       waitcnt(clkfreq+cnt)
    
       serial.tx(26)     'Send the message via CTL+ Z
       waitcnt(clkfreq+cnt)
       
       Receive
    
    
    PUB Receive | Value, ptr
                                             
      serial.rxflush                      'Flush Buffer
      ptr:=0                              'Set index pointer of the dataIn Array to location 0.
      Value:= serial.rxStr(@datain)       'Points to the address of the dataIn array.                  
      byte[datain][index]:=Value          'Writes the first Byte Received to the dataIn array pointed to by "ptr".
      ptr++                               'Increments the dataIn Arrays Index Pointer.
      
      repeat
        if dataIn[0] == "A"
           LED(0)
           
        if dataIn[0] == "B"
           LED(7)     
    
           
    PUB LED( whichOne )
          !outa[ whichOne ]
          waitcnt(clkfreq+cnt)
          !outa[ whichOne ]
          waitcnt(clkfreq+cnt)
          
    PUB WAIT
      waitcnt(clkfreq+cnt)
    
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-06 10:56
    Which rxStr function do you talk about? The one we created in post #1 and #2 or the one in Extended FDSerial?

    The one in the object reads a string until you entered CR or until you have entered 15 characters. So, if you enter "A" and want the code to react immediately it's the wrong function. Dito if you don't want to enter CR. It really waits for 15 characters or CR.

    What's wrong with rxStr from post #2?

    Is "A" go and "B" stop? And why do you want to read 2 characters? What is the purpose of the second character? In your code you only checked the first one. I'm sorry, but I am totally lost in all your questions, you should simply show what the expexted input looks like and what should happen in this case, for example:
    A<CR> -> light LED 0
    B<CR> -> light LED 7
    AX
    go<CR> -> start blinking LED 0
    stop<CR> -> stop blinking LED 0
    go
    stop
    ????
  • John MichaelJohn Michael Posts: 37
    edited 2011-12-06 11:50
    I apoligise MagIO2,
    I want to use Extended_FDSerial: PUB RxStr (stringptr) : Value | ptr or whatever is needed for the following ...

    I want to either Text the TeLit the word "Go" and have it Turn on LED_0 for one second or Text it "Stop" and have it turn on LED_7 for one second.
    The reason for the second character is that I will have many commands that I want it to respond to in the future and therefore need to understand the whole parsing thing. Right now I just want to understand how the Code should be written for these two commands.

    All commands in the future will be less than 15 characters long, therefore ...

    go "Blink LED_0 for one second"
    stop "Blink LED_7 for one second"

    Thank you for taking the time to Help ...
    John Michael
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-06 12:20
    Ah ... ok ... i see

    If you want to have an expandable command-line interface, then it's good to read the whole command first. If you later on have commands that need parameters it's good to read the whole line first and have a general parser which processes the whole line.

    You could for example have a look into the ConfigReader-object I put into the tools-section of the object exchange. It currently reads a command-line from a file, but it can easily be changed to read from serial interface instead.

    I think a good idea - also used in Mike Green's basic - is to handle commands by converting it with a hash function into a long-value. Then you don't have to store all the command-strings and you don't have to loop over all strings to identify the right command. Calling the right function is simply a case statement which compares the hash-value of the command just entered with the pre-calculated hash-values of the existing commands.
  • TubularTubular Posts: 4,708
    edited 2011-12-06 12:22
    Hi John

    * Use the RxCheck function which does not wait forever for characters to be received. This lets you timeout, if your comms link is broken for instance.
    * The RxCheck needs to be inside the loop where you are waiting for command characters. ie inside the Repeat loop (or I guess a different cog but that complicates things too much)
    * See the code example I posted for how to process single character commands using the RxCheck function. That code I know works
  • UnsoundcodeUnsoundcode Posts: 1,532
    edited 2011-12-06 18:38
    Hi John, if you have no header or zero terminator on your strings you could try something along the lines of this example. The crux of it is in the str_search method which takes a pointer to a string and returns the index from the predefined strings in the DAT section if a match is found.

    The method also works for single characters.

    If no match is found the method returns number of strings plus 1, in this case that would be 7. The first repeat in the method should be Repeat number of strings. The returned index value could be used in a CASE instruction to control your LED's

    It needs a little work but might give you a few ideas
    VAR
     byte  buffer[10]
     byte  array[10]
     Long stack[32]  
    PUB main | idx,ptr,value,temp
    
       ser.start(31, 30, 0, 9600)                                   
       ser.rxflush
     
     repeat
     
       bytefill(@array,0,10)
       
         repeat idx from 0 to 10                                                  
            value := ser.rxtime(10) 
            if value > 0            
              array[idx] := value     
            else
              quit
             
       temp:=str_search(@array)
       
          if temp>0 and temp<7
              ser.tx(temp)
     
    PRI str_search(strptr) |  idx,ptr,value,temp                                                
    
      value:=1
      ptr:=0
                                                    
      repeat 6
           bytefill(@buffer,0,10)
           
           repeat idx from 0 to 10                                            
             temp := byte[@my_cmds][ptr++]
      
              if temp > 0            
                buffer[idx] := temp     
              else
               quit
               
          if strcomp(@buffer,strptr)
            
            quit
     
        value++
        
      return value
        
    DAT
    my_cmds     byte "stop",0,"start",0,"forward",0,"backward" ,0,"left",0,"right",0,0
    

    Jeff T.
  • John MichaelJohn Michael Posts: 37
    edited 2011-12-07 05:30
    Jeff, thank you for taking the time to help with an example. I will study this example and ask question's ...
    Greatly appreciated,
    John.
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-07 23:23
    @Unsoundcode
    I did not run the code, but from my analysis I'd say it only works when some conditions are met - which is not good!
         repeat idx from 0 to 10                                                  
            value := ser.rxtime(10) 
            if value > 0            
              array[idx] := value     
            else
              quit
    

    You always want to read 10 bytes max., but actually if the user does not enter the next character within 10ms the user has to restart input. The preferred solution which is used since centuries ;o) is, that the user can input as long as he wants and when he's done, he simply hits the ENTER key. This is by the way what the extended FD function rxStr is doing for you - no need to reinvent the wheel.

    In str_search ... why do you limit the number of commands to 6 ( repeat 6 ). Your second 0 at the end of the list would be a perfect marker for EOL (end of list). So, you would stop the outer-loop if a pointer points to a zero-byte instead of to the next command. This way you can easily extend the command list without changing the number.
    Why do you copy the command into a buffer for comparison? As each command is 0-terminated you can simply compare the strptr directly with the string stored in my_cmds.
    PRI str_search(strptr):value | ptr                                                
    
      value:=1
      ptr:=0
                                                    
      repeat until byte[@my_cmds][ptr]==0
          ' compare actual command in the list with the input and quit if it's the same
          if strcomp(@my_cmds+ptr,strptr)
            quit
    
         ' forward pointer to the next command
         repeat until byte[@my_cmds][ptr]==0                                            
             ptr++
    
         ' fix: move the pointer behind the stringend of the command, so it either points to the first character of the next command
         ' or to the second 0 (EOL) of the list
         ptr++
    
         value++
      
      ' if command has not been found at all, return 0 (which is the same as FALSE)
      if byte[@my_cmds][ptr]==0
         value:=0
    
      ' return can be ommitted because value is the return value for the function and
      ' SPIN takes care of returning it
        
    DAT
    my_cmds     byte "stop",0,"start",0,"forward",0,"backward" ,0,"left",0,"right",0,0
    

    The return value of 0 is used whenever "command not found".

    And again ... with a hash-algorithm you would save memory (16bytes in this easy example) and runtime as hashing with a case-statement is much faster than looping over all command-strings.

    PS: sorry for that, but my code is untested because I'm currently at work and I don't have more time to spend even if I have my QS with me ;o)
    PPS: Brain-debugging showed me that the ptr++ was missing (marked with "fix:" in the comment) - having a good breakfast seems to help ;o))
  • UnsoundcodeUnsoundcode Posts: 1,532
    edited 2011-12-08 06:16
    Hi MaglO2, thanks for the input. I did say there was room for improvement and your comments are exactly what is needed.
    You always want to read 10 bytes max., but actually if the user does not enter the next character within 10ms the user has to restart input.

    I did it this way as a preliminary because I did not see where there was any kind of ETX and it was intended to take care of a pre defined string attached to a "speed" button. If the string should have a newline,carriage return or a zero that would indeed be better.

    From my point of view I was not particularly happy that "main" continually looped through the whole procedure, I would prefer a STX to trigger the events, but I typed it up this way because I thought it would broadly cope with the current situation.
    Your second 0 at the end of the list would be a perfect marker for EOL

    The second zero was an after thought placed there just before I posted, and I agree it is a better solution.

    I think the main point is to keep things modular and portable to other applications, str_search is looking pretty good right now.

    Jeff T.

    EDIT: I don't know what a hash algorithm is so I do what I can with what I understand, but it will be something to read up on.
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-08 10:32
    Yes, Jeff ... you are right ... from the point of the current requirements we have read in the previous posts it looks like a ENTER key is not in scope. So I should have addressed this topic to John. Maybe he can give us a little more insight of who/what is sending these commands? Is it another device where the protocol is already fixed? Or is it a device where you have the chance to define the protocol by yourself? Is it a human?
    In the last two cases I'd suggest to use the ENTER-key approach instead of an automatic timeout!

    Ok ... let's go on with learning!

    A hash function simply takes something as an input - for example a string or a bytestream (let's simply call it a stream) - and somehow calculates for example one LONG value for that input. So, if you input the same stream twice, you will get the same result. A good hash-function will spread the result over the whole number-range of a long even if you only have little changes in the input-stream.

    So what is the purpose of this?

    It's exactly to help with problems similar to the 'find the right command'-task. Let's assume you have to store a number of random words coming from ... wherever ... in an fixed array of strings AND later on you would like to find these words as fast as possible.

    You could easily store one word after another, but when you have to find the words you have to scan the whole array.
    You could store them one after another but sorted. When you have to find the words it's easier because you can use binary search. But when inserting a new word you might need to move the whole array to keep it sorted.
    With hashing you simply have a function which calculates the index out of the word itself and simply store the value there. No sorting and easy to find.

    In detail there is a little problem because two different words could create the same index. In this case you have to re-hash for example by calculating another index using a re-hash function or by an algorithm like .. if the index is already occupied by a word use the next index ...
    But even then you have less to do in average in the end compared to the other ways to deal with this kind of problems.

    Ok .. here is some code from a command interpreter I have:
    .... here is the code reading the input ....
    
        ' generate a hash value from the command only, so we can have buildin commands
        opcode := Hash(@key_buffer)
        
        ' check if the opcode is known, or if a *.COG has to be executed
        case opcode
          $0007997E: ' scan
             ' ... here is the code/function-call for the command scan
    
          $0006EC30: ' help
             ' ... here is the code/function-call for the command help
    
          $0D9B88E8: ' vstrsearch
             ' ... here is the code/function-call for the command vstrsearch
    
          $00AAC983: ' listexec
             ' ... here is the code/function-call for the command listexec
    
          $0D7CBAC2: ' vputstr
             ' ... here is the code/function-call for the command vputstr
               
          $0006D8B5: ' free
             ' ... here is the code/function-call for the command free
    
    ... a lot more commands following ...
    
    pri Hash(string_ptr) : Result | x
     result := 0
     repeat strsize(string_ptr)
       Result := (Result << 4) + byte[string_ptr++]
       x := Result & $F0_00_00_00
       if (x <> 0)
        Result := Result ^ (x >> 24)
       Result := result & !x
    

    So, this is effective as you only loop over the key-input once to create the hash-value and there is no need to store all the command-strings themselves.



    PS: Maybe I should have used the word key instead of stream because that's how hashing is used most of the time. You have a hash-map for storing values by using a unique key to identify each value.
    For our problem storing is not necessary at all.
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-08 16:36
    MagIO2, I am sending "Text" commands with my Cell phone To a TeLit 865-cf Cellular / GPS Combo Evaluation board which is interfaced to the Propeller. The mode is to set pass the Text I am sending straight through to the Prop on my PDB. All I really want to do is compare the incoming Text string to "Go" and "Stop" or the like and run sub-routines based on what "Text"command that I send. Just want to have unlimited range "Remote Control" so I can operate Servo's, Solid State Relays, ect.
    I think due to my in-experience I have complicated things, I have never even heard of half the things you are talking about :>) Can you give me a Very simple example of the Best way to accomplish this?

    John.
  • Mike GMike G Posts: 2,702
    edited 2011-12-08 17:10
    JMLStamp2p, what about framing up the command? For example, you could send !C1<cr>.

    ! = Attention
    C = Command
    1 = Data
    <cr> = carriage return (0x13)

    When you see an "!" you know the next byte is an identifier followed by the data which ends with a <cr>.
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-08 18:13
    Hi Mike, to be very Honest I am so confused on how to structure this thing right now I don't really know where to start. Can you give me a short example ...
    I bet I have tried 100 different ways to do this and nothing seems to work, new at Programming in Spin :<(

    John.
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-08 23:25
    New at programming or new at programming in SPIN? Anyway ... let's shed some light, so that you can see more clearly ... and maybe ask one or two more questions to see what your wishes are.

    Questions:
    1. Do you prefere the slower "find out by yourself"-approach where we only give you some hints and you still have to fiddle some things out by yourself?
    This is the way I would prefere because it is the way where you learn most! Doing your own mistakes and solving them is confirmed by neurologists to be the most effective way of learing.
    2. Dou you prefere the fast way "let us deliver the solutions which fit ... say 99% to your problem". This I'd only suggest if you have some time-constraints to meet in your project.
    Of course you can also learn your lessons this way, but it does not leave the same traces in your brain and maybe a half year later you will ask the same questions again not remembering this thread. ;o)

    As long as I don't have the answer, let me continue with some general concepts of technical problem solution.

    My understanding of your problem is, that you are the master of the protocol send from the cell phone to the propeller. So, actually the question is: which protocol makes sense here.
    Choice a): a pure technical protocol. You can send binary data, which is more effective than command-strings. For example you could send
    0 -> go
    1 -> stop
    plus 254 other commands
    Choice b): a human readable format.

    The advantage of a) is that you can send more commands in the same time and you don't need conversion for example from string to long-values as you send it binary. But ... you have to be sure that sender and receiver do understand correctly. You can not easily have a look at the traffic by yourself for debugging/analysis or for replacing the sender.
    The advantage of b) is that you don't need the sender to be implemented when starting with the receiver. You can for example simply read the input you send to the program via terminal-program (like the parallax PST). This splits the problem in two independend and smaller parts which is therefore easier to implement. Even later when you attach the receiver to the real sender you can listen to the data and easily inspect it.

    So, what's the prefered solution for your problem - my guess is b) because I don't see bandwidth constraints or the need to send 10,000,000 commands per second.

    Ok ... let's make the receiver run with a connection to the PC using the PST first.

    So, you already mentioned the Extended Full Duplex driver which has the rxStr function, right? Simply use that to read the input into a string-buffer.
    Then you can pick the hash-function code from my last post and call it with a pointer to that string-array.
    For the beginning you can simply output the opcode back to the PC using the hex-function of the full duplex serial driver to see what hash-codes are produced with wich commands.
    Then you can start implementing the case-statement also given in code of my last post to call your actual go and stop code.

    Viola ... first step accomplished! I can post some more code in the evening - when I'm back home - which will also parse parameters in a nice way and give you an own implementation of the rxStr which
    a) has a bigger input-buffer (because for commands having parameters 15 characters seems to be quite small)
    b) already prepares some things during input to make parsing easier
    So in the end you can read commands with up to 10 parameters which can be
    46577 - decimal input
    %010101 - binary input
    $AF45 - hex input
    aslfjlslfjljk - unquoted string
    "sldfj slfjklsj ljsfkj" - quoted string which allows having spaces and unparsed numbers
    All parameters are stored in an parameter-array where all number format inputs are already converted to long and for strings you will find a pointer to the string in the key-buffer.
  • Mike GMike G Posts: 2,702
    edited 2011-12-09 05:45
    @JMLStamp2p, the code below uses the Parallax Serial Terminal to read commands, execute logic, and display feedback on the terminal. Run the code, open the Parallax Serial Terminal, type one of the 5 [Case Sensitive] command in the terminal, and press enter. Also please see the Parallax Education Kit found in the Propeller Tool Help dropdown.

    Commands:
    LED16
    LED17
    LED18
    EchoOn
    EchoOff
    CON
        _clkmode = xtal1 + pll16x
        _xinfreq = 5_000_000
    
    
    DAT
            led16   byte "LED16", 0    
            led17   byte "LED17", 0
            led18   byte "LED18", 0
            echoOn  byte "EchoOn", 0
            echoOff byte "EchoOff", 0
            error   byte "Unknown Command ", 0           
        
    VAR
      byte  echo
      byte  rxString[50]  'Holds incoming serial bytes
      
     
    OBJ
      pst : "Parallax Serial Terminal"
    
      
    PUB Main
      'pst.StartRxTx(31, 30, 0, 115200)
      pst.start(115200)
      Pause(500)
      
      ' Echo the command
      echo := 1 
      pst.str(string("Receive String Demo",13))
      pst.str(string("--------------------",13))
      pst.str(string("LED16.....Toggle Pin 16",13))
      pst.str(string("LED17.....Toggle Pin 17",13))
      pst.str(string("LED18.....Toggle Pin 18",13))
      pst.str(string("EchoOn....Echo the command on the Terminal",13))
      pst.str(string("EchoOff...Do not echo the command",13))
      pst.str(string("..........................................",13)) 
      pst.char(13)
      
      Pause(500)
      
      repeat
      { Flush the Rx buffer}
        pst.RxFlush
    
        { Read a string }
        pst.StrIn(@rxString)
        
        { Compare the received string to the DAT strings }
    
        ' If echo is on we'll echo the command back to the caller
        if( strcomp(@rxString, @echoOn) )
          pst.Str(@echoOn)
          pst.NewLine
          
          echo := 1
          next
    
        if( strcomp(@rxString, @echoOff) )
          pst.Str(@echoOff)
          pst.NewLine
          
          echo := 0
          next
    
        { Commands }
        ' LED 16 command handler
        if( strcomp(@rxString, @led16) )
          if(echo == 1)
            pst.Str(@led16)
            pst.NewLine
          
          Toggle(16)
          next
    
        ' LED 16 command handler  
        if( strcomp(@rxString, @led17) )
          if(echo == 1)
            pst.Str(@led17)
            pst.NewLine
          
          Toggle(17)
          next
    
        ' LED 16 command handler  
        if( strcomp(@rxString, @led18) )
          if(echo == 1)
            pst.Str(@led18)
            pst.NewLine
          
          Toggle(18)
          next
    
        pst.Str(@error)
        pst.NewLine
        Toggle(23)
    
        
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return     
             
             
    PUB Toggle(pin)
      dira[pin]~~
      repeat 50
        !outa[pin]
        waitcnt(5_000_000 + cnt)
    
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-09 06:53
    MagIO2,

    I am more of an Artist than a Programmer and a Very Visual person. I have enough Tech. in me to really want to understand the in's and outs of why the code works so my answers to your questions are as follows:
    I would prefer to take the Receive code a line at a time and "Comment" what I understand it to accomplish; not only for myself but for others to benefit as well. If my understand is not correct, please make adjustments to the comments so they will help me understand. I would like to use "Text" as a communication medium for this project so that "non-Tech." individuals can operate the interfaced equipment from their Smart Phones. If they want to turn on a water pump, they simply just text "Turn on the water pump :>)
    I beleive that a person retains things better from the thinking process as we "re-wire" our Brains continually but have also learned this: If I try to teach a person to play the Guitar by just teaching them note's and scales I'll most likely bore the "Real Artist" to death until they just give up. The Left Brained guitart player may stick with the notes and scales but never really incorperate a Stylish flare into their playing and never really "Hear the Music". If I teach the "Real Artist" a Song they like that incorperates the same notes and scales they will play forever and learn without really thinking about it :>)
    My point, I am that "Creative Right Brained" type that needs to learn by Having Fun a step at a time with the project or the whole thing becomes a Bore ...

    Thank you for your patience as I work through the notes ...
    John.

    Mike G:

    Thank you for your posted code, I will try it this afternoon when I get home.
    Much appreciated.
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-09 12:19
    Hi Mike ... thanks for your code.

    I translated it into the version which uses hashing. Of course due to the command-length and the small number of commands it's not smaller because there is the cost of adding the hash-function. But even with this little amount of short commands it's only 3 longs bigger.
    CON
        _clkmode = xtal1 + pll16x
        _xinfreq = 5_000_000
    
    
    DAT
            error   byte "Unknown Command ", 0           
        
    VAR
      byte  echo
      byte  rxString[50]  'Holds incoming serial bytes
      long  opcode 
      
     
    OBJ
      pst : "Parallax Serial Terminal"
    
      
    PUB Main
      'pst.StartRxTx(31, 30, 0, 115200)
      pst.start(115200)
      Pause(500)
      pst.CharIn
      
      ' Echo the command
      echo := 1 
      pst.str(string("Receive String Demo",13))
      pst.str(string("--------------------",13))
      pst.str(string("LED16.....Toggle Pin 16",13))
      pst.str(string("LED17.....Toggle Pin 17",13))
      pst.str(string("LED18.....Toggle Pin 18",13))
      pst.str(string("EchoOn....Echo the command on the Terminal",13))
      pst.str(string("EchoOff...Do not echo the command",13))
      pst.str(string("..........................................",13)) 
      pst.char(13)
      
      Pause(500)
      
      repeat
      { Flush the Rx buffer}
        pst.RxFlush
    
        { Read a string }
        pst.StrIn(@rxString)
        
        { Compare the received string to the DAT strings }
        opcode := Hash(@rxString)
    
        case opcode
          $04B9F45E: ' EchoOn
            pst.Str(@rxString)
            pst.NewLine
          
            echo := 1
            
          $0B9F4586: ' EchoOff
            pst.Str(@rxString)
            pst.NewLine
          
            echo := 0
            
          $00509746: ' LED16
            if(echo == 1)
              pst.Str(@rxString)
              pst.NewLine
          
            Toggle(16)
    
          $00509747: ' LED17
            if(echo == 1)
              pst.Str(@rxString)
              pst.NewLine
          
            Toggle(17)
            
          $00509748: ' LED18
            if(echo == 1)
              pst.Str(@rxString)
              pst.NewLine
          
            Toggle(18)
    
          other:
            pst.Str(@error)
            pst.Hex(opcode,8)
            pst.NewLine
            Toggle(23)
        
    pri Hash(string_ptr) : Result | x
     result := 0
     repeat strsize(string_ptr)
       Result := (Result << 4) + byte[string_ptr++]
       x := Result & $F0_00_00_00
       if (x <> 0)
        Result := Result ^ (x >> 24)
       Result := result & !x
         
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return     
             
             
    PUB Toggle(pin)
      dira[pin]~~
      repeat 50
        !outa[pin]
        waitcnt(5_000_000 + cnt)
    

    I will go on with this code and add the other features I talked about in the earlier posts. The LED commands are a perfect example where I'd use a command parameter solution.

    PS: The only thing added to the functionality is that in case of an incorrect command, the error-message also gives the hash value of the entered command so that you can use it in the code.
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-10 06:53
    Good morning, would someone Verify that I am understanding this Code correctly.
    VAR 
          long   rxStr_Stack[20]
          byte   rxStr_Array[20]
    
    OBJ
          serial: "Extended_RDSerial"                                     'give us access to the method's included in the Extended_FDSerial Object.
    
    PUB MAIN
    
          cognew(rxStr(3, @rxStr_Array), @rxStr_Stack)         'Starts a new Cog to run the rxStr method.
          rxStr(3, rxStr_Array)                                                'Call the rxStr method and tell it to receive 3 byte's
                                                                                       'use the rxStr_Array to hold the byte's received.
    
    PUB rxStr (num_of_chars, strPtr) | char, index                'rxStr method, receives the # of chars to receive and strPtr
                                                                                       'is the "pointer" varible that holds the starting location of the 
                                                                                       'rxStr_Array memory Block.
                                                                                       
          index:= 0                                                               'index pointer points to location "0" 
                                                                                       'of the rxStr_Array.
    
          repeat                                                                    'repeat the following code as long as it's condition is TRUE
              char:= serial.rx                                                   'set the private varible "char" to the value of the char received.
              if num_of_chars == 1                                           'if we have received a char ...
                  Toggle(0)                                                        'toggle the output of Pin - 0.
    
            byte[strPtr][index]:= char                                       'write the first "char" to the indexed location which is "0", of the Array pointed to 
                                                                                       'by strPtr which is the rxStr_Array.
    
            index++                                                                'increment the index pointer so the next byte "char" received will be saved
                                                                                        'to location 1 of the rxStr_Array.
    
           if num_of_chars- - == 3                                           'if we have received all the chars "3" which was passed to the rxStr method
                                                                                        'in it first parameter execute the following indented code statment.
              Toggle(7)
                                                                                        'if we have not received "3" chars loop back to get another one.
    
    
    
  • Mike GMike G Posts: 2,702
    edited 2011-12-10 07:43
    JMLStamp2p, it looks like there are gaps in your understanding. Even us artistic types need to study and practice.

    A couple of suggestions:
    1) Post error free code when asking for help. You code does not compile.
    2) Post all your code not just the part you think in causing the issue
    3) Open up the Propeller manual and read about the Spin methods your are invoking
    4) Go through the Propeller Education Kit. The PE kit has a lot of great examples.

    I believe this is what you are trying to do...
    CON 
      _clkmode = xtal1 + pll16x
       _XinFREQ = 5_000_000
    VAR 
      long   buffer[20]
    
    OBJ
      serial: "FullDuplexSerial"                                    
    
    PUB Main | bytesToReceive, i
    
      serial.start(31, 30, %0000, 115200)
      Pause(1000)
      serial.str(string("Recieve Bytes Demo",13))
      serial.str(string("------------------",13))
    
      i := 0
      bytesToReceive := 10
      repeat
        if(getBytes(bytesToReceive))
          serial.str(string("Received "))
          serial.dec(bytesToReceive)
          serial.str(string(" Bytes: "))
          
          repeat bytesToReceive
            serial.tx(buffer[i++])
          serial.tx(13)
    
          i := 0 
          Toggle(16, 20)
                                                          
                                                                                      
    
    
    PUB getBytes(bytesToReceive) | char, count
      count := 0
      repeat until count == bytesToReceive
        char := serial.rxcheck
        
        if char > -1
          buffer[count++] := char
        else
          Toggle(23, 2)
    
      return count
          
         
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return     
             
             
    PUB Toggle(pin, times)
      dira[pin]~~
      repeat times
        !outa[pin]
        waitcnt(5_000_000 + cnt)
    
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-10 08:18
    I'm trying Mike :>)
    Thank you for your suggestions and you Code, I'll give it a whirl ...
    John.
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-10 09:10
    Mike, I have looked in the PE Kit Labs and do not see anything that really gets down to the details of reading, writing & parsing String array's. Am I missing it ? Do you have a Link to something I could study that would really help me understand them?
    I am looking over the Propeller manual as well ...

    John.
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-10 10:03
    Mike, this code is to verify that I understand your code statements correctly. Please re-comment anything that you see that is wrong in my assessment, I have added the INITIATE method just to show the AT commands that set up the TeLit for communication pass through to the Propeller "The TeLit will not pass the bytes through correctly without it". The INITIATE method compiles alone correctly on my platform and sends the message "Initialize Complete" as intended. Please don't take this complete showing of code to compile togather as it is just a referance to others as we work togather for a complete project.
    Thank you for your Help,
    John.
    '------------------------------------------------------------------------------------------------
    '    Cellular Serial Code, "In Progress"  - using the TeLit 865cf and the Propeller PDB.                
    
    'NOTE: I'm using a TeLit 865 Evaluation Board with a Professional Development Board from Parallax.
    'The two are connected via NULL Modem Adapter and a 9-Pin Serial Cable, both are considered DCE's.
    'Pin's 10 & 11 have a soldered jumper to disable Hardware Hand Shaking on the TeLits side.
    
    '------------------------------------------------------------------------------------------------
    CON 
      _clkmode = xtal1 + pll16x
       _XinFREQ = 5_000_000
       
    VAR 
      long   buffer[20]
      long   Receive_Stack[20]          '20 longs of stack space for the new Cog to use.
      byte   index                      'A varible called "index" to use as a "pointer" varible.
    
    OBJ
      serial: "Extended_FDSerial"
      
    PUB MAIN | bytesToReceive, i                            'Public Method MAIN with Local varibles
    
    
      serial.start(26, 25, %0000, 115200)                   'Set up serial port pins, mode & baud rate.
    
      INITIATE
      
      Pause(1000)                                           
      serial.str(string("Recieve Bytes Demo",13))           'Display "Receive Bytes Demo".
      serial.str(string("------------------",13))
    
      i := 0                                                'The varible "i" is set to 0.
      bytesToReceive := 10                                  'Set the number of bytes to receive.                                         
      repeat                                                'Repeat the following.
        if(getBytes(bytesToReceive))                        'If the getbytes function has received the number of bytes you requested
                                                            'Display the number of bytes received.
          serial.str(string("Received "))
          serial.dec(bytesToReceive)
          serial.str(string(" Bytes: "))
          
          repeat bytesToReceive                             'Getting another 10 bytes
            serial.tx(buffer[i++])                          'Transmit the 10 bytesthat are in the "buffer".                                                  
          serial.tx(13)                                     'Transmit the equivelent of "Enter".
    
          i := 0                                            'Set the varible "i" back to 0. 
          Toggle(1, 3)                                      'Toggle Pin(1), 3 - times.
                                                          
                                                                                      
    
    PUB getBytes(bytesToReceive) | char, count              'Getbytes method, "accepts" number of bytes to receive.
      count := 0                                            'Initialize the varible count to "0".
      repeat until count == bytesToReceive                  'Repeat the following untill you have received 10 bytes.
        char := serial.rxcheck                              'char is set to the byte that has entered the serial port.
        
        if char > -1                                        'If we have received the first character ...
          buffer[count++] := char                           'Let the Value of whats in the buffer := char and Increment the varible count.
                                                            '                                               .
        else                                                'If we have not received the first byte ...
          Toggle(7, 2)                                      'Keep calling the Toggle method and blinking LED - 7.
    
      return count                                          'Return the value of count to the calling routine.
          
         
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return     
             
    PUB INITIATE
    
       
       serial.str(String("AT+CMGF=1",13))
       'Set TeLit to
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT#SMSMODE=0",13))
       'Set TeLit to SMS Text mode.
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CPMS=ME",13))
       'Set TeLit to
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CNMI=2,2,0,0,0",13))
       'Set TeLit to
       waitcnt(clkfreq+cnt)
    
       serial.str(String("AT+CMGS=",34,"+1**********",34,13))
       'Install your cell number.
       waitcnt(clkfreq+cnt)
    
       serial.str(String("Initialize Complete"))
       'Message to Send.   
       waitcnt(clkfreq+cnt)
    
       serial.tx(26)                                                
       'Send the message via CTL+Z
       waitcnt(clkfreq+cnt)
    
       Toggle(7)
    
                                                  
             
    PUB Toggle(pin, times)
      dira[pin]~~
      repeat times
        !outa[pin]
        waitcnt(5_000_000 + cnt)        
    
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-10 11:58
    Hi together!
    PUB getBytes(bytesToReceive) | char, count
      count := 0
      repeat until count == bytesToReceive
        char := serial.rxcheck
        
        if char > -1
          buffer[count++] := char
        else
          Toggle(23, 2)
    
      return count
    
    PUB Toggle(pin, times)
      dira[pin]~~
      repeat times
        !outa[pin]
        waitcnt(5_000_000 + cnt)
    

    I see some conditions in this code which propably work for the current problem, but you should know about them if you want to use this piece of code somewhere else or if you do some small changes (increas the number of blinks) and find out that the code no longer works.

    In the getBytes-function you call Toggle which only blinks an LED. But the toggle also waits, which is not the best thing to do if you have a function that should receive bytes from an external device. You wait for 1/8th of a second which is a real long time for a controller. With the 115200 bits per second you can receive 1800 bytes in this time which is slightly bigger than the buffer available in the FullDuplexSerial.

    So, I would use a different way for just blinking the LED. At the beginning of getBytes you could setup a counter and let it blink the LED. This also makes the getBytes-loop much easier, as you onls need to say

    ' setup counter with output on LED-Pin....
    repeat bytesToReceive
      buffer[ count++ ] := serial.rx
    ' stop counter
    

    For the counter-code you can have a look at the PE Kit Labs.
  • Mike GMike G Posts: 2,702
    edited 2011-12-10 13:03
    JMLStamp2p, the code I posted is designed to help you understand basic concepts. It's more a learning tool in keeping with your statement, If I try to teach a person to play the Guitar by just teaching them note's and scales I'll most likely bore the "Real Artist" to death until they just give up. Think of the code above as a song. you are free to take any piece you like and make it your own. If you have trouble with a couple of bars, don't hesitate to ask for help.

    However, I will not write the song for you. The music has to come form you. The reason I'm making this statement is once again you have posted code that does not compile which means you did even try to play the song.

    Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime
    Mike, I have looked in the PE Kit Labs and do not see anything that really gets down to the details of reading, writing & parsing String array's. Am I missing it ? Do you have a Link to something I could study that would really help me understand them?

    Strings seem to trip up a lot of folks. A String is an array of bytes that end with a zero. Take a look at the string methods in the Propeller manual as well as the BYTE type.
    STRCOMP
    STRING
    STRSIZE
  • JMLStamp2pJMLStamp2p Posts: 259
    edited 2011-12-12 10:21
    Mike, I do not have a Propeller platform at work and therefore cannot always compile code immediately. From this point on I will wait until I compile even if I have to wait until the next day to get back with you ...
    Thanks for your patience and your help,
    John.
  • TubularTubular Posts: 4,708
    edited 2011-12-15 03:49
    John, how did you go with the code I sent, did it do the job?
Sign In or Register to comment.