element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Test & Tools
  • Technologies
  • More
Test & Tools
Blog understand TinyUSB USBTMC protocols
  • Blog
  • Forum
  • Documents
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Test & Tools requires membership for participation - click to join
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 4 Oct 2023 4:14 PM Date Created
  • Views 2980 views
  • Likes 10 likes
  • Comments 25 comments
  • pico_usbtmc_scpi
  • USBTMC
  • Pico SCPI labTool
  • labview
  • scpi
Related
Recommended

understand TinyUSB USBTMC protocols

Jan Cumps
Jan Cumps
4 Oct 2023

In this post, I attempt to understand the flows that the USBTMC class driver follows for:

  • standard SCPI query / response
  • standard SCPI command only
  • *STB? status byte requests
  • service requests
  • triggers

For each scenario, I'll try to figure out how the logic flows, what flags are set or cleared, payloads, etc...

SCPI Query: *IDN? 

main.c tud_task()
usbd.c tud_task_ext()
  case DCD_EVENT_XFER_COMPLETE 
    if ( 0 == epnum ) -> else
usbtmc_device.c usbtmcd_xfer_cb()
  case STATE_IDLE
    case USBTMC_MSGID_DEV_DEP_MSG_OUT:
usbtmc_device.c handle_devMsgOutStart()
usbtmc_device.c handle_devMsgOut()
usbtmc_app.c tud_usbtmc_msg_data_cb()
  if(transfer_complete && (len >=1)
    query_received = true;
    queryState = 1
    scpi_instrument_input();

Reply

main.c usbtmc_app_task_iter()
usbtmc_app.c usbtmc_app_task_iter()
  case querystate 1-> 2 case querystate 2:
    querystate 3:
    set MAV 0x10
    set SRQ 0x40
  case querystate 3:
  // here I reply. I (or you :)) need to check what happens if the SCPI engine didn't generate acommand. 
  // that happens if the SCPI was a command, not a query.
  // will it then do the right thing?

After a reply is complete, the usb_tmc.c state machine calls  tud_usbtmc_msgBulkIn_complete_cb()

start side note 1

One thing I found, is that when I send a command (does not reply) instead of a query (replies), tud_usbtmc_msgBulkIn_complete_cb() is not called and MAV isn't reset....
This call originates from the usbtmc_device.c. A file that I do not have control over.

I am taking the "executive" decision" to clear that flag myself if the SCPI didn't return a value (because it got a command instead of a query). This is how I rewrote case 3 (transmit). Check the else clause at the end:

 case 3: // time to transmit;
    if(/* TODO check if I can just ignore this*/ bulkInStarted &&  (buffer_tx_ix == 0)) {
      if(reply_len) // if this was a SCPI query, there will be a reply.
      {
        tud_usbtmc_transmit_dev_msg_data(reply,  tu_min32(reply_len,msgReqLen),true,false);
        queryState = 0;
        bulkInStarted = 0;
        reply_len = 0;
      }
      else
      {
        buffer_tx_ix = tu_min32(buffer_len,msgReqLen);
        tud_usbtmc_transmit_dev_msg_data(buffer, buffer_tx_ix, buffer_tx_ix == buffer_len, false);
      }
      // MAV is cleared in the transfer complete callback.
    }
    else { // this is not thread safe. When you port this to a multi-threaded device, take care to put guard rails
      // jc 20231004: when there is no reply from the scpi engine (the input was a command, not a query)
      // mark as if a reply was sent
      // MAV is cleared here
      if(! reply_len) { 
        // if this was a SCPI query, reply_len woulkd be > 0
        // and tud_usbtmc_msgBulkIn_complete_cb() would clear all of this.
        uint8_t status = getSTB();
        status &= (uint8_t) ~(IEEE4882_STB_MAV); // clear MAV
        setSTB(status);
        queryState = 0;
        bulkInStarted = 0;
        buffer_tx_ix = 0;
        query_received = false;
      }
    }

This code should only be called when a SCPI string is sent that does not generate a reply (a command instead of a query)

end side note 1

start side note 2

or should I not set the MAV bit? if there's no reply? In the end, it's the Message Available Bit

end side note 2

todo

  • Sign in to reply

Top Comments

  • ggabe
    ggabe over 1 year ago in reply to Jan Cumps +1
    The SRQ bit is only set after a certain delay (125ms), for strictly testing purposes with the included python test client. I rebuilt the test example, and the python test cases passing pedantically.…
  • ggabe
    ggabe over 1 year ago in reply to Jan Cumps +1
    The tinyusb build puzzles me. I wanted to add to following function to usbtmc_device.c (and I did), but the usbtmc_app.c breaks at linking time. I don't know what else to say in the tinyusb codebase, neither…
  • ggabe
    ggabe over 1 year ago in reply to ggabe +1
    Some progress: creates a proper Visa handled SRQ, when invoked from tud_usbtmc_msg_trigger_cb, no errors in IO Trace: index 0cf0743a7..e94e7c062 100644 --- a/src/class/usbtmc/usbtmc_device.c +++ b/src…
Parents
  • Jan Cumps
    Jan Cumps over 1 year ago

     ggabe , the TinyUSB example sets SRQ bit (0x40) in the STB when it receives a SCPI command. https://github.com/hathach/tinyusb/blob/b394ae1786911a89ea3437d7090cda477ca7df6c/examples/device/usbtmc/src/usbtmc_app.c#L219


    I ported this to the firmware

    Are you aware that this is normal? I start to expect that this is a test case, not default behaviour ...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to Jan Cumps

    The tinyusb build puzzles me. I wanted to add to following function to usbtmc_device.c (and I did), but the usbtmc_app.c breaks at linking time. I don't know what else to say in the tinyusb codebase, neither the other app functions, like tud_usbtmc_start_bus_read(void); declares anything special to get included in the library.

    bool tud_usbtmc_send_srq(void); 

     

    usbtmc_app.c:(.text.tud_usbtmc_msg_trigger_cb+0xc): undefined reference to `tud_usbtmc_send_srq'

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to ggabe

    It requires a substantial amount of code duplication for typedefs to enable 'extern ... usbtmc_state;' to function correctly.

    Instead, I'm trying a build script, to patch tinyusb. This is how the section of the build script looks like:

    git clone git@github.com:raspberrypi/pico-sdk.git
    cd pico-sdk
    git submodule update --init
    cd ..
    
    CURRENT_DIR=$(pwd)
    
    export PICO_SDK_PATH=$CURRENT_DIR/pico-sdk
    
    cd $PICO_SDK_PATH/lib/tinyusb/src/class
    git apply $CURRENT_DIR/usbtmc_device.patch
    cd $CURRENT_DIR
    
    git clone https://github.com/j123b567/scpi-parser.git
    cd scpi-parser
    git submodule update --init
    cd ..



    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to ggabe

    I may have found a way to do this. Not pretty - but the two attributes coming from usbtmc_state are fixed after initialisation:

    #include "usbtmc_device_custom.h"
    #include "device/usbd_pvt.h"
    
    // TODO remove this USBTMC customisation once TinyUSB supports send srq
    #if (CFG_TUD_USBTMC_ENABLE_488)
    // TODO I hardcoded this based on the PICO definition in tusb_config.h
    #define PATCH_usbtmc_state_rhport (BOARD_TUD_RHPORT)
    // TODO I hardcoded this based on the PICO definition in usb_descriptors.c
    #define PATCH_usbtmc_state_ep_int_in (0x82)
    bool tud_usbtmc_send_srq(void) 
      {
        usbtmc_read_stb_rsp_488_t rsp;
        
        rsp.bTag = 0x01; //Indicates SRQ
        if (true)  // TODO validate: (usbtmc_state.ep_int_in != 0)
        {
          rsp.statusByte = 0x00; // Use interrupt endpoint, instead. Must be 0x00 (USB488v1.0 4.3.1.2)
          // if(usbd_edpt_busy(usbtmc_state.rhport, usbtmc_state.ep_int_in))
          if(usbd_edpt_busy(PATCH_usbtmc_state_rhport,PATCH_usbtmc_state_ep_int_in)) // see patch defines above
          {
            rsp.USBTMC_status = USB488_STATUS_INTERRUPT_IN_BUSY;
          }
          else
          {
            rsp.USBTMC_status = USBTMC_STATUS_SUCCESS;
            usbtmc_read_stb_interrupt_488_t intMsg =
            {
              .bNotify1 = {
                  .one = 1,
                  .bTag = 0x01 //Indicates SRQ
              },
              .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status))
            };
            // Must be queued before control request response sent (USB488v1.0 4.3.1.2)
            // usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, (void*)&intMsg, sizeof(intMsg));
            usbd_edpt_xfer(PATCH_usbtmc_state_rhport, PATCH_usbtmc_state_ep_int_in, (void*)&intMsg, sizeof(intMsg)); // see patch defines above
          }
        }
        else
        {
          rsp.statusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status));
        }
        //TU_VERIFY(tud_control_xfer(usbtmc_state.rhport, request, (void*)&rsp, sizeof(rsp)));
        return true;
      }
    #endif
    

    It compiles. Can you share the test script you used to validate the functionality?

    source: https://github.com/jancumps/pico_scpi_usbtmc_labtool/tree/issue-47-without-patch-docker-linux

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to Jan Cumps

    I'll share the test python code later today. In my prior experiments, when setting a watch on usbtmc_state_ep_int_in, I was getting 0x81 (129 decimal), so I hardcoded it that way. Never worked. I wonder how stable this value will be across build. I'll be testing your 0x82 value as well. 

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to ggabe

    The SRQ test. While running, push the button on GP27, several times.

    import pyvisa
    import time
    from pyvisa import ResourceManager, constants
    import sys, re, traceback, logging
    import time
    
    rm = pyvisa.ResourceManager('@ivi') 
    
    logging.basicConfig(level=logging.ERROR)
    
    def handle_event(resource, event, user_handle):
        print(f"Handled event {event.event_type} on {resource}")
        #resource.called = True
        stb = instr.read_stb()
        instr.write("*CLS")
    
    with rm.open_resource("USB0::0xCAFE::0x4000::E660C06213865126::INSTR") as instr:
    
        print('Opened\n')
        
        instr.called = False
    
        event_type = constants.EventType.service_request
        event_mech = constants.EventMechanism.handler
        wrapped = instr.wrap_handler(handle_event)
        user_handle = instr.install_handler(event_type, wrapped, 42)
        instr.enable_event(event_type, event_mech, None)
        
        instr.write("STAT:OPER:DIGI:INP:NTR 4\n")
        instr.write("STAT:OPER:DIGI:INP:ENAB 4\n")
        instr.write("STAT:OPER:ENAB 1\n")
    
        instr.write("*CLS")
        instr.write("*SRE 128") #leaving Bit6 MSS - off
        
        print('Done setting up')
           
        cv = 0
        
        try:
            while not instr.called:
                time.sleep(0.01)
                    
        except: 
            instr.close()
            logging.exception("While looping")
    
    try:
        instr.disable_event(event_type, event_mech)
    except:
        print('err while disabling event')
    try:
        instr.uninstall_handler(event_type, wrapped, user_handle)
    except:
        print('err while disabling event')
    
    instr.close()
    print("Done.")

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to ggabe

     Jan Cumps putting a reply here, so Jan can get a notification. The thread is having some issue, not scrolling to the end. Need to click the "new" to see the last comments.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • ggabe
    ggabe over 1 year ago in reply to ggabe

     Jan Cumps putting a reply here, so Jan can get a notification. The thread is having some issue, not scrolling to the end. Need to click the "new" to see the last comments.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to ggabe

    there's a (not obvious) View More link when scrolling comments

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to ggabe

    No success yet (with 0x82):

    I added the tests to the existing test set:

    def handle_event(resource, event, user_handle):
    	print(f"Handled event {event.event_type} on {resource}")
    	stb = inst.read_stb()
    	inst.write("*CLS")
    
    
    rm = visa.ResourceManager()
    reslist = rm.list_resources("USB?::?*::INSTR")
    print(reslist)
    
    if (len(reslist) == 0):
    	sys.exit()
    
    i = 0
    inst = None
    while i < len(reslist):
    	inst = rm.open_resource(reslist[i]);
    	inst.timeout = 3000
    	inst.clear()
    
    	print("+ IDN")
    	if(test_idn()):
    		print("Instrument found")
    		# reset
    		inst.write("*RST")
    		# test 3 pins
    		j = 0
    		while j < 3:
    			test_pin(j)
    			j += 1
    	print("SCPI errors during test: "+ inst.query("SYST:ERR:COUNT?"))
    	print("Basis Test complete")
    
    	print("Experimental test")
    	inst.called = False
    	event_type = constants.EventType.service_request
    	event_mech = constants.EventMechanism.handler
    	wrapped = inst.wrap_handler(handle_event)
    	user_handle = inst.install_handler(event_type, wrapped, 42)
    	inst.enable_event(event_type, event_mech, None)
    	
    	inst.write("STAT:OPER:DIGI:INP:NTR 4\n")
    	inst.write("STAT:OPER:DIGI:INP:ENAB 4\n")
    	inst.write("STAT:OPER:ENAB 1\n")
    
    	inst.write("*CLS")
    	inst.write("*SRE 128") #leaving Bit6 MSS - off
    	
    	print('Done setting up')
    	   
    	cv = 0
    	
    	try:
    		while not inst.called:
    			time.sleep(0.01)				
    	except: 
    		inst.close()
    #	   logging.exception("While looping")
    
    	try:
    		inst.disable_event(event_type, event_mech)
    	except:
    		print('err while disabling event')
    	try:
    		inst.uninstall_handler(event_type, wrapped, user_handle)
    	except:
    		print('err while disabling event')
    	
    	inst.close()
    	i += 1
    
    print("All Test complete")

    Looping here:

     while not inst.called:
        time.sleep(0.01)

    I'll try debugging when I have some quality time ....

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 1 year ago in reply to Jan Cumps

    My build with the patch file applied over Pico/TinyUSB:      pico_scpi_usbtmc_labtool.uf2      

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to ggabe

    ... confirming that this firmware works  with the test script ...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube