Shop OBEX P1 Docs P2 Docs Learn Events
Sending/Receiving ASCII strings for an SCPI interface — Parallax Forums

Sending/Receiving ASCII strings for an SCPI interface

I am new to SPIN2 and this is my first object based language, so be warned of possible ignorance in what I'm about to say. I am using the P2 to interface with several test components. I have several analog inputs and digital inputs/outputs. I have figured out how to interface with all of those "dumb" components thus far and am able to use the PST to see the changing analog inputs and correctly time my digital outputs and see the digital inputs. I am now trying to interface with a multimeter which uses SCPI(Standard Commands for Programmable Instruments) language. It is a serial connection and expects ASCII strings in an 8 bit with 1 stop bit, no parity bit configuration. I am using a second instance of the jm_fullduplexserial object to setup this serial connection but cannot figure out how to transmit and receive these ASCII strings as usable data. I have tried sending strings, hex strings, and binary bytes and the multimeter just displays on its front panel that it is getting either a formatting and/or overflow error. I've looked for other SPI objects that might work better but all my googling is no producing any results. Has anyone dealt with an SPI connection using SCPI or ASCII data that could just point me in a direction to keep trying because I feel like I'm hitting a brick wall. Thank you very much in advance.

Comments

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-04-05 15:24

    I "talk" with lots of devices using ASCII strings. As in C, Spin strings are a group of characters followed by a null (0). You can predefine them in a DAT section.

    dat
    
      s_GetDcVolts  bayte   ":MEAS:VOLT:DC?", 0
    

    You can send this with the .str() method.

      serial.str(@s_GetDcVolts)
    

    If you're going to use a string more than once, this is the best practice. If it's a one-off thing, you can also embed the string inline.

      serial.str(@":MEAS:VOLT:DC?")
    

    The compiler will embed the string (with terminator) in the call.

    What I suggest is you create a little serial bridge that lets you type in commands with PST and sends them to the device and then listens for a response. It's pretty easy, and can be very useful as you can explore the device and decide which commands should be predefined for your application. I've done this a couple of times for devices with a simple serial interface.

  • I think the pst command idea is great and would like to be able to send it input commands from the PST so I can determine it's full functionality before I design the HMI to control it, but that seems far off from where I'm at currently. The maximum baud rate of the device is 19.2k, so I am setting up the pst using tstart() at baud 230400 and then instancing jm_fullduplexserial again and using start() to give it the tx/rx pins and the 19.2k baud rate. Would I be able to setup two independent serial connections with different pins using jm_fullduplexserial if I set the PST baud to 19.2k, put commands in the pst, have them come back to the P2, and then out to the SCPI multimeter?

    s_GetDcVolts bayte ":MEAS:VOLT:DC?", 0

    Is that what the ", 0" is saying, that it will terminate the string with a null(0)? This device has a setting for flow control and tx terminator. I have flow control off and the terminator set to carriage return.

    Where I am currently is that I initialized that ASCII string in dat and send it using .str() but it still gave "Invalid character", "RS-232 Overrun detected", and "RS-232 Break detected" errors. I do understand how the .str() will format and pass the ASCII bytes from dat so thank you very much for that. I'm just not sure if it is trying to pass more than 8 bits with a stop bit and then ceasing transmission. I also then need to receive and decode the information back from it. What I'm currently doing to see a change is setting it to DC volts manually and then sending :CONF:CURR:AC to see if it will change to that. This is only if I can get it to stop going into an error loop.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-04-05 16:51

    Would I be able to setup two independent serial connections with different pins using jm_fullduplexserial ...

    Yes. You can have two instances of jm_fullduplexserial running on different pins at different baud rates.

    Is that what the ", 0" is saying, that it will terminate the string with a null(0)?

    For strings in DAT sections you have to add the 0 manually (as shown). If you use string() or the newer @"" syntax, the compiler adds the 0 for you.

    ...but that seems far off from where I'm at currently.

    Have a look at the attached. It was a 15-minute coding-over-coffee exercise that should get you started. Set the device TX and RX pins for your hardware and give it a try. The comments should make everything clear. It uses a small buffer so you can create your command and fix something with backspace before sending (command is sent on pressing CR).

    Have fun

  • I can mostly understand this code with a few exceptions. It starts by clearing the command buffer and going to a new line with >>, designating that it will echo what I put in the command line. When I type in the CONF:CURR:AC, I believe the code is putting that into the command buffer with a max size of 64 characters and echoing it after the >> in the pst. When I press enter is it adding a carriage return to the end of the buffer and then transmitting it to the device? After that I believe I should see a << with what the device returned but it is still giving the framing error. Is a null(0) being added to the transmitted string because I believe that would be the issue for the framing error. It could also be that I'm mistaken that hitting enter from the command line adds a carriage return. I also wanted to thank you for your help as I understand more than I did before which is always my goal.

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-04-05 18:58

    Is a null(0) being added to the transmitted string

    No. The str() method uses strsize() to count the characters before the null and only sends them.

    It could also be that I'm mistaken that hitting enter from the command line adds a carriage return.

    You can set the KEEP_CR = NO to see if that fixes the problem. I have this because some devices do not want that character. I use the buffer approach so that I can edit the command and transmit in one go -- in the past I've dealt with Bluetooth radios that would timeout because I couldn't enter the commands quickly enough.

    I also wanted to thank you for your help

    You're welcome. This is kind of interesting.

    Once you get coms sorted you'll then have to parse the data coming back from the device. That's more involved, but not too hard. In between tasks I wrote this method that will convert values from the SCPI stream; it understands integers in decimal, hex, octal, and binary format. No floats, though. I rans several test cases; I feel pretty good about it.

    pub extract_scpi_int(p_str) : value | sign, base, c, d
    
    '' Returns integer value from SCPI string
    '' -- integers only (no floats, no exponents)
    '' -- leading sign (+ or -) for decimal values okay
    
      sign, base := 1, 10                                           ' assume positive decimal
    
      repeat
        c := byte[p_str++]
        if (c == "-")
          sign := -1
          quit
        elseif (c == "+")
           quit
        elseif (c >= "0") && (c <= "9")                             ' decimal detected
          p_str--                                                   ' back up for next section 
          quit
        elseif (c == "#")                                           ' base indicator?
          c := to_upper(byte[p_str++])
          if (c == "H")                                             ' hex
            base := 16
            quit
          elseif (c == "Q")                                         ' octal
            base := 8
            quit
          elseif (c == "B")                                         ' binary
            base := 2
            quit
          else
            return 0                                                ' parsing error
    
    ' extract the value    
    
      repeat
        c := to_upper(byte[p_str++])                                ' get char from stream
        c := lookdown(c : "0123456789ABCDEF")-1                     ' convert to number
        if (c >= 0) && (c < base)                                   ' legal for base?
          value := value * base + c                                 ' add to value
        else
          quit
    
      value *= sign                                                 ' update with sign
    
  • Jon you've been very forthwith with your help and so I wanted to really take a second and third look at this without coming back to you empty handed. I started scoping the transmitted signals and sending known hex values. I've determined that this RS-232 device operates on a -10 /+10V logic scale and only accepts an input if it starts low. Scoping the transmitted bits from the P2 I've found that in normal mode(%0000), it starts at logic high(3.3V) and goes low. I changed modes to inverted tx(%0010) in both my code and your code and I now have compatible communication from both the PST and transmitting dat variables. I've learned a lot about your fullduplexserial code and I wanted to thank you again. Now that I have the logic levels correctly established it's as easy as sending a byte string and just using ", 13" as the escape character in the dat variable.

    I have development remaining to decipher the readings back from the device, but I will have to build a level shifter first. I'm sure with what I understand now about spin2 and the serial object I will be able to figure it out. You're a real gem and this may be the most helpful tech forum I've ever posted in. Usually I throw myself at the mercy of the community and get told I probably didn't google enough. So, sincerely, thank you again.

  • If you're connecting directly to an RS-232 level port, you should insert a 50K resistor between the device TX and the P2 RX; this will limit the current through the pin clamping diodes to a safe level. Or, as you suggest, you can insert a TTL to RS-232 level shifter. If you add this circuitry, return your serial settings to True mode (non inverted). The MAX232 is a popular device that requires a few external caps. My friend John (owner of JonyJib) uses this in his Propeller-based products (I write the code for him) for communicating with RS-232 devices.

    Thank you for the kind words. You'll find a lot of helpful people in these forums. The key is to make an effort (as you are) and to be specific about what you're trying to accomplish. When possible, share code or circuits that are giving you problems. Do as much as you can to help others help you (I did have to Google SCPI since it was new to me).

    Good luck with your project.

  • I built a crude level shifter for dealing with the dev_rx before I read this. I used a rectifying diode combined with a voltage divider to change the +-10V signal to a 0-3V. I am able to scope what the P2 pin is seeing and its a roughly 8 ms word, a 4 ms delay, and then some escape or ending character. However, attempting to read this and print it to the pst using either your serial bridge code or the extract_scpi_int() method you shared is only printing gibberish or a lower case y.

    I looked up what you referenced and found that "Low-cost Bidirectional Mixed-voltage Interfacing" pdf which level shifts perfectly. However, I am still not able to output to the PST anything that could be construed as data from the multimeter. I am using that serial bridge code and I'm not sure I understand how get_device() is supposed to be working. I understand that it only runs if there is data on the buffer, will print <<, run rxtime() for 25 ms, and then print whatever it added to the buffer if there was anything there to add. However I only get a lowercase y. I have changed baud rate to 4800 and increased timeout to 50 ms just in case but to no avail. I'm just not sure how to see if the raw data I'm seeing on the scope is even being read by the P2 and how to output that raw data as something readable if it is being read.


    Value from Ammeter


    Scoped input data from Ammeter(Green)


    PST output

  • JonnyMacJonnyMac Posts: 9,102
    edited 2023-04-06 19:48

    Check that you're using inverted TX and RX with your device. The code I uploaded may have been set to True mode (not inverted). Your setup() method should look like this

    pub setup()
    
    '' Configure IO and objects for application
    
      term.tstart(BR_TERM)                                          ' start terminal serial (true)
    
      dev.start(DEV_RX, DEV_TX, %0011, BR_DEV)                      ' start device serial (inverted)
    

    The get_device() method starts by checking the RX buffer; if the device has responded it will drop through until it hits a timeout (device is finished).

    Do you have a logic analyzer? You can get a dirt cheap version from Amazon and use it with a nifty piece of freeware called PulseView.
    -- https://www.amazon.com/gp/product/B077LSG5P2

    It's fine for low-speed stuff like this. I keep a spare in my computer bag and it has helped while on the road a few times. This image is from a Zoom class on serial that I presented for Parallax (during the pandemic lockdowns). The protocol analyzer in PulseView supports inverted mode, so you should be able to see the messages from the P2 point-of-view.

  • It was the configuration! I didn't realize there was a bit for changing RX as well as TX. I thought that one bit changed both! This just went from a Good Friday, to a great Friday. Yesterday I was manually analyzing the bits on the scope and today I have a fully functioning RS-232 interface. Thank you so much!

Sign In or Register to comment.