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
  • About Us
  • 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
Join Test & Tools to participate - click to join for free!
  • 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 3840 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 2 years 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 2 years 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 2 years 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 2 years 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 2 years 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 2 years ago in reply to ggabe

    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/class/usbtmc/usbtmc_device.c
    @@ -880,4 +880,42 @@ bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request
       }
     }
     
    +#if (CFG_TUD_USBTMC_ENABLE_488)
    +bool tud_usbtmc_send_srq(void) 
    +  {
    +    usbtmc_read_stb_rsp_488_t rsp;
    +    
    +    rsp.bTag = 0x01; //Indicates SRQ
    +    if(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))
    +      {
    +        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));
    +      }
    +    }
    +    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
    +
     #endif /* CFG_TUD_TSMC */
    +
    diff --git a/src/class/usbtmc/usbtmc_device.h b/src/class/usbtmc/usbtmc_device.h
    index c1298ddb8..fb3fcd873 100644
    --- a/src/class/usbtmc/usbtmc_device.h
    +++ b/src/class/usbtmc/usbtmc_device.h
    @@ -95,6 +95,9 @@ bool tud_usbtmc_transmit_dev_msg_data(
     
     bool tud_usbtmc_start_bus_read(void);
     
    +#if (CFG_TUD_USBTMC_ENABLE_488)
    +bool tud_usbtmc_send_srq(void); 
    +#endif
     
     /* "callbacks" from USB device core */
     
    @@ -104,6 +107,7 @@ bool     usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result,
     bool     usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
     void     usbtmcd_init_cb(void);
     
    +
     /************************************************************
      * USBTMC Descriptor Templates
      *************************************************************/
    

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

    Good news: SRQ callbacks work with PyVisa!

    Beside the diff above to TinyUSB, this update makes it complete, in 

    /source/usb/usbtmc_app.c

    void setControlReply () {
      tud_usbtmc_send_srq();
    }
    
     

    In PyVisa, when the event handler fires, it only needs to acknowledge with a *CLS
    Fairly robust, not seen any crashes or contentions (none is expected, per the single threaded polling based approach to events on the Pico side of the house)

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

    Do we have to put tud_usbtmc_send_srq() in the tinyusb sources?

    Maybe if we put it in our sources, and declare:

    extern ... usbtmc_state;

    We can leave the lib sources alone.

    Then suggest a pull request for the tinylib, and remove our code once it becomes part of a picosdk ....

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • ggabe
    ggabe over 2 years 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 2 years 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
Comment
  • Jan Cumps
    Jan Cumps over 2 years 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
Children
  • ggabe
    ggabe over 2 years 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
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