David explains how to implement a SCPI "Skippy" instrument control command parser in C++
Forum: http://www.eevblog.com/forum/blog/eevacademy-9-implementing-scpi-in-c/'>http://www.eevblog.com/forum/blog/eevacademy-9-implementing-scpi-in-c/
EEVblog Main Web Site: http://www.eevblog.com
The 2nd EEVblog Channel: http://www.youtube.com/EEVblog2
Support the EEVblog through Patreon!
http://www.patreon.com/eevblog
AliExpress Affiliate: http://s.click.aliexpress.com/e/c2LRpe8g
Buy anything through that link and Dave gets a commission at no cost to you.
Stuff I recommend:
https://kit.com/EEVblog/
Donate With Bitcoin & Other Crypto Currencies!
https://www.eevblog.com/crypto-currency/
T-Shirts: http://teespring.com/stores/eevblog
Likecoin – Coins for Likes: https://likecoin.pro/ @eevblog/dil9/hcq3
Forum: http://www.eevblog.com/forum/blog/eevacademy-9-implementing-scpi-in-c/'>http://www.eevblog.com/forum/blog/eevacademy-9-implementing-scpi-in-c/
EEVblog Main Web Site: http://www.eevblog.com
The 2nd EEVblog Channel: http://www.youtube.com/EEVblog2
Support the EEVblog through Patreon!
http://www.patreon.com/eevblog
AliExpress Affiliate: http://s.click.aliexpress.com/e/c2LRpe8g
Buy anything through that link and Dave gets a commission at no cost to you.
Stuff I recommend:
https://kit.com/EEVblog/
Donate With Bitcoin & Other Crypto Currencies!
https://www.eevblog.com/crypto-currency/
T-Shirts: http://teespring.com/stores/eevblog
Likecoin – Coins for Likes: https://likecoin.pro/ @eevblog/dil9/hcq3
Okay, so today we're going to be talking about Skippy. This is a a set of commands like format. The standard commands for programmable instruments Skippy commands are separated into these sections and they're separated by colons. So here we have the first node, the second node, and the third node.
And when you see these in document specifying how to communicate with instruments, you often get this strange syntax is like you get these square brackets and this means optional and so this is something that we're going to have to implement. Commands are always separated by these colons. Each of these commands has two sections. It's a required section and an optional section.
The required section is in uppercase and the optional section is in lowercase. Now, Skippy is not strictly case-sensitive so this is this command could be written as instrument or instrument or instrument. Those are all valid and the required section is, of course, always required. but the optional section isn't so you could abbreviate that node as I and s T or I N S T Or if you're really weird, I N St.
And yeah. so after the different nodes, you get these parameters and parameters are always separated by commas. And so here we have three different parameters: A1, A2, and A3 and this is quite typical. Before the parameters, you always see a white space, but it's an optional white space between them.
It doesn't have to be there. So this is what we're going to implement. so let's just go ahead and do that. So I called this a command before I've called a keyword here and as I stated, it has two different sections, a required section and an optional section.
And these are just strings in an embedded system You wouldn't use stood string, you would use a fixed size array and I'll talk about that at the very end for people who care. but if you just want a higher level overview, third string is probably the easiest way to understand it, so that's what I'm going to do. So here we have a the required section being defined in the constructor and the optional section being defined in the construction and the optional section is optional because you might have a keyword which is something that is always required such as the required commands in skippy. These these are things like test and reset and identification.
They all this entire command is required so this entire keyword is required. So the optional section is actually optional and keywords are quite simple. They consist of an O and it requires section at the start which is the capital letter section and then if that is found you then can pass an optional section and the optional section. It it but obviously by the name may exist or may not and it always returns true.
The function that looks for the optional section is always found because something that's optional always exists kind of. So we return true after we find that. So let's have a look at how that works. Now all of these functions take two parameters, all of the parts of functions. that is, they take the block which is to be processed and an index which to start processing and they always will return a boolean that is whether it is found or not. Or alternatively, if it's not an optional we're talking about, it's whether the index changes or not. So here we have a keyword and the keyword consists of Hell which is all required so hell is required and then lo ello which is optional. So if we were to process it, we can say if keyword hello with an index and if if that is true then we know it's found and I'm going to step through that function to show how it works.
So when you enter the function, we have two parameters. We have hello and we have the index. index is initially 0. When the required section is found, the index increments by the length of that that section so that the length of that section is 3 H E L 1, 2 3.
So index is incremented by 3 which means the start the character will be processed next is at index 3. So and in this case, as you can see from this array is an L a lowercase L which means it's optional. So and then the optional section begins processing at that index. So if it finds it which it does, it increments it by the length of the section that it found.
So that's 5. Now at now the index is 5, which means it was incremented by that. That optional section of length 2 3 plus 2 is 5. There we go.
So and then it returns true because it found it. So that's sort of the structure of the The Skipper command. But how do we actually determine whether we found a required section or an optional section? So let's step through it again. So we step in again.
and now we're going to step into required. So we've taken a keyword. So we're looking for H E L and we're looking for it in the block H-e-l-l-o and we're starting at index 0. Here we start, we start by initializing a temporary index to zero and that index is going to go through the keyword and determine how much of the keyword is found, and when it, when it finds all of it, it will kind of indicate that.
So here we go Initially, we are checking H the first letter of keyword and and then we calculate the block index which is just how far are we into this for loop plus the offset, which is the starting index. So if if the block index is within the bounds of the block, then we can check the current letter, which is basically the start point of the block for the first iteration. Then the next one will be the next letter and the next one will be the next letter So so we're saying, is it the same letter and if it is, if current is the same as required and it is, then we just continue. and we just churn through all these letters until either we reach the end of the keyword with index or the block.
Index is greater than the block size which means it breaks and if it breaks, that means it basically wasn't found Because we didn't completely find, we didn't completely find the keyword before the block ended. So then all we have to do is test whether the index is equal to the keyword size because if the index has incremented by the number of characters in keyword, then we know that we actually have found every character in keyword. And in this case, we have So then all we do is say index plus equals that length of keyword index on its final iteration, increments once more, so it actually equals the length. So the length of it is three, so we expect it to increment to three. So index is now three. Which means that we have finished processing the first three characters in the block. There we go returns true And that means that we can now start process in the optional section and optional is basically identical to required, but it always returns true. So it's it's really simple to implement.
It calls the required function it can nor as the result and returns true. Yep, great. That was easy. So now you know how to kind of implement a parser for the Skippy keywords.
Now let's work on the next section. Okay, so now we have to implement the node. and the node is actually just a keyword with a with a colon in front of it. That's this and the root node.
The first node in Skippy that's this node here is actually optional. It's the only optional :. So we have to have this all ears root flag and you don't really have to understand templates to understand this. You just have to know it's either true or false when you create one of these classes.
And if it's true, then the colon is optional. and if it's found, it increments the index past the colon. If it's not, it doesn't and then that means we're free to start processing the keyword and we process the keyword exactly the same as we did in our test case. So after we process the keyword, we know that we've found the whole, the whole node.
So we're free to increment the index and then we return true because it is found. Now, if it's if it isn't the root node of the first node, then we're kind of fine to say the colon is required. So this means that it's always found and it will always increment index by one if it is found. So if the colon is found and then the keyword is found, then we can do the same thing as before.
We're ready to if it means the whole node is found and we can increment index and ink and return true. So let's try that out. So a node and we're going to do a non root node. It's a bit simpler and a node consists of a key word and the key word here is hello.
We can probably just do equals. So the node is going to be constructed from this keyword here and we use the function exactly the same way only. Now we're looking for that :. So if we run it, it won't to be found because we don't have the colon and we're going to step into it and we step into it and this is not a root node. so it runs the required. So the required colon in this block here is not going to be found and we return false because we haven't found it. So that's what should happen. Now if we add a colon here, the required colon is now inside the block and index is free to increment by one.
and it has. So now we start looking for hello and we're starting at index one now and it's found and the index is now incremented to the end of the keyword. So and we know the keywords found, so we know we've found this node. So Skippy is really just a bunch of nodes and all we have to do to actually implement a full Skippy parser is use these nodes in basically some if statements.
So in this case we're going to look at these commands. We're going to look at Sense so a standard command in Skippy would be sense and this could be followed for your voltage or something. So that's what we're going to do. And well, I've added one more thing which is query and I Just look at this function before we go through.
This query. just detects the question mark character, increments the index if there is one, and returns true. so that's all it does. So we have three nodes in this.
We have the Sense node which is got the required Sen s and with an optional A at the end. Then we've got a voltage node with the required Vol T and an optional A GE And then finally we have current which is an option with a required CU RR and an optional ENT. Here's a table, the required section and the optional section. Now again, this is not case-sensitive so these could be upper or lower case.
Ok, so how do we actually process a command? Well, first we have to take the command as an input see in just gets gets a string and then we pass the string to our Sense node. A Sense node is like a function and the Sense node takes the input in the index just as before. and if it finds it, that means we know that we found this section. We found this SE NS E or SE NS or some variation of capitals and lower cases.
So if we've done that, we're free to go to the next section. So because we're looking for sense voltage or we're looking for since current looking for either or either of those but not both, we have voltage. If that's found, then we process that else. If current is found, we process the current so it's quite simple and then so in inside the processing of those two, it's either a query or a command and the query.
In this case, it's just going to print whether it's a query or not with a question mark. But usually this would mean that you would either either return a value or assign a value. So it's It's relatively simple, so let's have a look. Let's run this program all right.
So here we go. So if we step through this program with the inputs sense voltage, then our input is is what we just entered into the console and our index starts at 0. And if we find the word sense and because it's in here, we will then index increments to the end of the word sense and then we're ready to process the next section of the command. So sense here ends at character 5. So we're now ready to process from character 5 onwards. and that's why index is 5. So now we don't know whether its voltage or current. so we have to test it.
and this if statement tests that. So if the voltage is found, it'll run this. So it does, because it is there. and it moves the index to the end of the word voltage which is 13 the null character inside the raw array.
We're at the right place and then we just determine whether it's a query or not. Now, it did not end in a question mark, which was that query thing we talked about just before. So it does not print a question mark. This is an If statement in in a single line.
This says if this is true, do this. Otherwise, do this. There colon separates the two. It's called a ternary operator.
It's kind of weird. Yeah, Ok, so then we just print it. and there we go. Since voltage found.
Now, what about current? What about current? So let's just continue through. So since current. So of course this is the same at the start. It's common between the two, so we're not going to process sense again each time.
That would be pointless. We're gonna process at once and it will continue to the next node and it will go to its child. Notes that that's these nodes. So voltage wasn't found.
So index stays at five, which is at the end of the word sense at the the :. Here we go. So now we're going to process from five onwards starting with the :. So this time we're going to step in so you can see kind of what it does.
so it says is the cold in there I Mean it has to be because it's not a root. So is it at character five inside the block it is. And now we're ready to search for the key word. The key word is the current.
So if currents found then we're going to continue. We're going to assign the new end index to the parameter P index and then we return true. Then we know. Finally, we have current and that's how Skippy's command.
That's how skip is interpreted and just as some variations. So if you didn't if you admitted the semicolon at the front, it should still work. No problem. And if you omit the optional characters, it should still work.
And if you now end it with a query, it works still. but this time it's detected that it is a query. So this is the basic structure of a Skippy interpreter. It's quite lightweight with the exception of using the standard string, and it's something you can put on your own devices, your own test equipment, which makes it much more usable for people who aren't aren't you? So if you look at the micro suppliers Skippy specification, we basically have everything that we talked about here.
before we have an optional semicolon at the start. optional things are put around square brackets and then we can have source the voltage and then we can assign it a value or we can ask for what it is. Now, skip is typically done by serial, so that's what we're going to do. That's what we've done, so let's try it out. So if we input source : voltage those the the source node, then voltage with a single parameter of 1.2 volts, it will set the source voltage to 1.2 It doesn't return anything here, but we can't query whether it was correct and this is usually a good idea. So there we go. We it returned 1.2 when I queried the voltage. How about source, current, source, current and we're going to assign zero point one, two three because that's awesome or something.
Source current query should return the same thing and it does. So this is basically a real-world example of a Skippy command interface, so of course it's not case sensitive as where tests weren't it does exactly the same thing. So yeah, it's a really useful interface. and if there's there's a few that here's the required commands that we have implemented.
and this is why it says Skippy ish because Skippy requires all of them. All of them. but we there's no practical use of implementing all of them. so for the minimum viable product, we are not doing that.
Maybe we'll do it later. So now the required commands start in a star. That's the only difference and they have to have that star. I Think So here we go: ID in with a query and there you go returns this query string build.
So if you want to get the build, you could query it. So the the query date of this is the the build date is October 12th and the build time was at six forty four forty seconds. So there's these er, this is a good command interface. It's something that is human readable.
It's something that that is easy to extend and build build applications with because all you need to do is send a simple string to the device through just a serial interface. I Mean serial serial interface libraries are common as mud and yeah, if we now type this voltage command with a different voltage sou RCE then we see it changes and we can of course query it and it returns the voltage. and you've got all these other commands and all these other commands. This you've got.
You got some measurement commands, you've got some output commands and the structure these commands might change. I Mean this is pretty preliminary I Probably should have written preliminary in the document name, but this is what we got. so hey cool. Okay, well hope you enjoyed the video I hope you learnt something and I hope this kind of pattern of pauses is useful to you because writing pauses can be a real pain if you don't have a standardized pattern and this is what I use generally.
So I hope you have a good day, have a good one bye in production I Don't use stood string I use a replacement using template metaprogramming if you want to see how to do that, anyone go full node with C++ which you probably don't the link isn't down below. Consider this a warning.
What is problem in String Compare??
Really poor C++ style.
Sorry, but this is ludicrous. Templates and metaprogramming for this little parser? I whish to see a real complier wriiten in this style, I bet it will have 1GB of sources… This parser can be written in 2 pages of proper code and a tree data structure for the SCPI.
as far as programming tutorial goes.. This one is piss poor..
Nice implementation David.
Wouldn't it be a whole lot safer to run your protocol in a state machine? Neat video to see your method of embedded C++ programming anyhow. Is this going to be open source? Is there a git repository somewhere (i.e. on GitLab or GitHub or something)?
When will I be able to buy a uSupply?
I understand what this is for, but as others have already highlighted a quick intro to why and what you are doing would have been nice.
DOOOD!
David, a two minute introduction would go a long way, i.e. a quick background to SCPI and what you are using it for. Just doing that will draw people in.
Proper way would be defining a grammar and generate a parser based on that using some existing tool.
Never heard of SCPI…
i couldn't see Skippy lol
Some pretty poor C++ code in there, not very well styled at all. I guess you're allowed a pass because you're not technically a software developer. At least it's not as bad physicists or god forbid mathematicians.
Thanks David, great demonstration! I'd be interested in a follow up video where you write a unit test case for it 😉
Thanks David! Really useful string parsing stuff, and elegant too.
I've written quite a few parsers in my life, for all kinds of data. But this is the most cumbersome implementation I've ever seen. Sorry. You should think in terms of a stream of characters, not blocks of characters.
I'm looking forward to seeing this power supply. Is it a complete redesign?
Just dont mention "The bush kangaroo" 🙂
Poorly structured tutorial.
you should always start a tutorial with at least a few sentences on "what is this for"