The Pico SCPI labTool (PST) allows you to connect your PC to equipment to control and monitor all sorts of things. It runs on a Raspberry Pico. It would be nice if the PST could supports device dependent SCPI registers. They can be used to notify a LabVIEW flow from the device. I'd be happy if we can notify LabVIEW when a GPIO input changed logic level. |
part 1: PST.. Experimental event / trigger support for Pico SCPI labTool - 1: investigate
part 2: PST.. Experimental event / trigger support for Pico SCPI labTool - 2: design the registers
part 3: PST.. Experimental event / trigger support for Pico SCPI labTool - 3: Instrument Specific Registers Test
part 4: PST.. Experimental event / trigger support for Pico SCPI labTool - 4: let TinyUSB USBTMC code use SCPI-LIB's Status Byte register
part 5: PST.. Experimental event / trigger support for Pico SCPI labTool - 5: Propagation to IEEE488.2 SCPI Registers Test
part 6: PST.. Experimental event / trigger support for Pico SCPI labTool - 6: Service Request from Instrument to LabVIEW flow
part 7: PST.. Experimental event / trigger support for Pico SCPI labTool - 7: Test Service Request from Instrument to LabVIEW flow
History: why do I have 2 status registers
When I started developing the firmware, I combined the TinyUSB USBTMC example with the a SCPI lib that I know well. But I didn't know a lot about the TinyUSB example.
I investigated what part of the example code received the SCPI commands, and what part sent the reply.
I connected the SCPI lib input and output functions to these two parts. Tested, and I got it to work.
I didn't make many more changes to the example code. Happy that it worked, and ran stable.
What I didn't understand at the time, was that the status variable in that example was mimicking the SCPI IEEE488.2 STB (Status Byte) register. I thought it was a variable to entertain the program's state machine.
When I started to dig deeper into SCPI registers, and got a better understanding, I realised that this status variable was in fact a partly implemented STB. Just enough to be able to test the TinyUSB USBTMC profile. Excellent for a test bed. But my project should have a single STB. I left the integration incomplete. And it prevented me to start using the Service Request functionality.
Compare the 2 STBs
The TinyUSB USBTMC state machine needs a STB. Nothing stops it from using the SCPI lib's one. It's actually the desired outcome. But how do I migrate?
TinyUSB sample code implementation
The example is deliberately minimal. It allows to let the your design enumerate as a Test and Measurement component. And it can listen and reply to a *IDN? command.
The STB in that example takes care that this functionality works.
#define IEEE4882_STB_QUESTIONABLE (0x08u) #define IEEE4882_STB_MAV (0x10u) #define IEEE4882_STB_SER (0x20u) #define IEEE4882_STB_SRQ (0x40u) static volatile uint8_t status;
In the state machine, the relevant bits are used to decide what the driver has to do. I'm happy with how they made this example: the author used the correct bits, and documents that.
SCPI lib implementation
That's a full standard compliant register implementation, with connections to Query register, Event register, Operation Status register, ...
I'm not presenting the code here, but will show two API calls that you can use to read and write the registers.
scpi_reg_val_t SCPI_RegGet(scpi_t * context, scpi_reg_name_t name); void SCPI_RegSet(scpi_t * context, scpi_reg_name_t name, scpi_reg_val_t val);
These are not just a getter and setter. They have (IEEE488.2 specified) side effects. Setting a value may fan out to other registers, if so configured. And a read may (or may not) clear a register - again based on the specs of the standard.
But having these two functions makes the migration easy.
Migration
First, I abused the compiler, to show me where the TinyUSB code uses the status. For that, I commented out the variable, and then compiled the code. Each compiler error indicates a line of code that uses the variable.
Then I put a TODO comment at each of these occurrences. VS Code has a Todo module, that will give you a list of all lines that you tagged with todo.
I now have a worklist of code to adapt.
My integration strategy was to create two bridge functions that will make the SCPI STB look, for the TinyUSB code, identical as the original variable.
uint8_t getSTB() {
return (uint8_t) SCPI_RegGet(&scpi_context, SCPI_REG_STB);
}
void setSTB(uint8_t stb) {
SCPI_RegSet(&scpi_context, SCPI_REG_STB, (scpi_reg_val_t) stb);
}
I then visited each of my Todos, and retrieved the SCPI STB with the helper before that code in a local variable (with the same name as the original one that I commented out).
After the code that manipulated the variable, I updated the SCPI LIB STB with the other helper function. Example:
bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg) {
(void)msg;
// Let trigger set the SRQ
uint8_t status = getSTB();
status |= IEEE4882_STB_SRQ;
setSTB(status);
return true;
}
Rinse and repeat until all Todos are gone, and code compiles again.
I tested extensively, to see if this refactoring has any impact on stability and functional code. All was good.