I'm building a SCPI electronics lab instrument for Linux. This post connects the C SCPI parser service together with the C++ Instrument service. I've split up the SCPI interpreter and the instrument control for my own learning. I'm trying to use daemons on a linux SOB that talk together. I could have used named pipes, messages or file structures to let them interact but I've used sockets. Critique in the comments please. |
What Came Before and Linking Things Together
In the previous posts I've shown the SCPI parser (a C program) and the PiFace controller (object oriented). The two worked on their own but not together.
Both programs are TCP/IP servers. They listen on a port and react on traffic arriving.
The SCPI server reacts on SCPI commands it receives over the network.
The Instrument server reacts on GPIO commands it receives.
I've added a TCP/IP socket client to the SCPI server. Whenever it receives SCPI commands that have to be executed by the PiFace Digital, it translates them and sends instructions to the Instrument server's port.
It waits for the instrument's feedback and returns SCPI replies based on that.
Currently it knows how to control the outputs of the PiFace Digital.
The SCPI parser listens for this SCPI command:
DIGItal:OUTPut# TRUE|FALSE
If it retrieves such a command, it sends the Instrument service the instruction to set the requested output pin high or low.
If you send DIGI:OUTP0 1, it will send write01 to the Instrument port.
This part is written in C:
{.pattern = "DIGItal:OUTPut#", .callback = SCPI_DigitalOutput,},
static scpi_result_t SCPI_DigitalOutput(scpi_t * context) { scpi_bool_t param1; int32_t numbers[1]; // retrieve the output. Can be 0 - 7 SCPI_CommandNumbers(context, numbers, 1, 0); if (! ((numbers[0] > -1) && (numbers[0] < 8) )) { SCPI_ErrorPush(context, SCPI_ERROR_INVALID_SUFFIX); return SCPI_RES_ERR; } /* read first parameter if present */ if (!SCPI_ParamBool(context, ¶m1, TRUE)) { return SCPI_RES_ERR; } memset(instrument_payload, '.', sizeof(instrument_payload)); instrument_payload[0] = 'w'; instrument_payload[1] = 'r'; instrument_payload[2] = 'i'; instrument_payload[3] = 't'; instrument_payload[4] = 'e'; (instrument_payload[5] = '0' + numbers[0]); // only works if the 0 - 7 characters in the character set are consecutive. todo: Sue me. instrument_payload[6] = param1? '1' : '0'; sendToInstrument(instrument_payload, sizeof(instrument_payload)); return SCPI_RES_OK; }
int sendToInstrument(char *buffer, size_t size) { //Send instruction to instrument buffer[size-1] = '\n'; if( send(cs_socket_desc , buffer , size , 0) < 0) // if( send(cs_socket_desc , "hello, world!\n" , 14 , 0) < 0) { puts("Send to instrument service failed"); return 1; } //Receive a reply from the server if( recv(cs_socket_desc , buffer , size , 0) < 0) { puts("Receive from instrument service failed"); return 1; } return 0; }
The Instrument server will know (because I've programmed it) that when it retrieves a write command, that the next two characters are the pin and value.
The Instrument server is written in C++:
while (stream >> x) { // process the commands if (x.compare(0, 4, "read") == 0) { // todo implement me } else if (x.compare(0, 5, "write") == 0){ pfd_write(std::stoi(x.substr(6, 1)), std::stoi(x.substr(5, 1)), PifaceDigital::OUTPUT, pf); // todo: parse argument }
(the alert reader will see that the code doesn't know yet what to do when receiving a read command. That's on the todo list.)
The actual write command is forwarded to the PiFace class that we've designed a few blogs ago.
void pfd_write(uint8_t value, int bit_num, uint8_t reg, PifaceDigital *pf) { pf->open(); if (bit_num >= 0) { pf->writeBit(value, bit_num, reg); } else { pf->writeReg(value, reg); } pf->close(); }
Top Comments