By connecting to the "ACM" port exposed on the USB of the STM32 (used as a service and connected to a PC) it is possible to send alphanumeric command (eg with "minicom") in order to perform: -Digital Output. -Switched pulse. -Digittal Input. -Analogic Input. -Request of a brief (returned by the serial line): 'H' or '?'. Note that all the commands can be given on a single string and it will be executed readily just at the instant each one is completely received.

Dependencies:   regex mbed

Revision:
0:2c26b4ba7cf3
Child:
1:cae05f3e5d56
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cpp	Tue Dec 19 09:23:12 2017 +0000
@@ -0,0 +1,612 @@
+/**
+ * Funzionalità Esposte
+ * Collegandosi alla porta "ACM" esposta sulla USB dell'STM32 (adibita a servizio e collegata p.e. ad un PC) è possibile inviare i
+ * seguenti comandi in formato testo (es. con minicom).
+ * -DigitalOutput: comando 'DO P<portName [B-G]> *<pinNumber [0-15]> <value [01]>;'.
+ * -Switch impulsivo: comango 'DP P<portName> *<pinNumber [0-15]> <time>;'.
+ *  Dal momento in cui viene chiamata l'uscita specificata viene negata fino allo scadere del tempo specificato.
+ *  Notare che può essere chiamata in modo rientrante e a raffica: l'effetto è esattamente quello programmato: vengono
+ * istanziati tanti "Timeout" ogni richiesta e disallocati dopo lo scadere del proprio tempo.
+ * -Digit al Input: comando 'DI P<portName> *<pinNumber [0-15]>;'. Ritorna '[01];' in base al livello di tensione
+ * presente sul pin specificato (0 o 1).
+ * -Analogic Input: comando 'AI <analogic input name (at the moment only [ADC_TEMP, ADC_VREF, ADC_VBAT])>;'
+ * Dove <time> è un valore float con precisione massima di 10^-3 Sec (es.: 0.00x è valildo, 0.000x non è valido).
+ * Risposta: 'OK;' per ogni comando corretto, '<ERROR <wrong sequence> [(Port=<portName>, Pin=<pinNumber>)][analogic input name])\n\r'
+ * in ogni altro caso.
+ * Risposta: 'OK;' per ogni comando corretto, 'ERROR <wrong sequence> [(Port=<portName>, Pin=<pinNumber>)][<analogic input name>])\n\r'
+ * in ogni altro caso.
+ * N.B.: i comandi sono riportati facendo uso della  seguente serie di espansioni:
+ * <s> per indicare una valore numerico o alfanumerico con significato 's'; [x-y] per indicare una range di interi da 'x' a 'y';
+ * [a,b,c,....] per indicare una serie di possibili stringhe (alfanumeriche) 'a', 'b', 'c',...; * e + come nelle standard re.
+ *
+ * Exposed functionality
+ * By connecting to the "ACM" port exposed on the USB of the STM32 (used as a service and connected to a PC) it is possible to send the
+ * following commands in text format (eg with minicom).
+ * -DigitalOutput: command 'DO P<portName [B-G]> *<pinNumber [0-15]> <value [01]>;'.
+ * -Switch switch: combo 'DP P<portName> *<pinNumber [0-15]> <time>;'.
+ * From the moment the specified output is called, it is denied until the specified time expires.
+ * Note that it can be called reentrant and burst: the effect is exactly the one programmed: many "Timeouts" are instantiated every request and disallowed
+ *  after the expiration of its time.
+ * -Digit to Input: command 'DI P<portName> *<pinNumber [0-15]>;'. Return '[01];' according to the voltage level present on the specified pin (0 or 1).
+ * -Analogic Input: command 'AI <analogic input name (at the moment only [ADC_TEMP, ADC_VREF, ADC_VBAT])>;'
+ * Where <time> is a float value with a maximum accuracy of 10 ^ -3 Sec (eg: 0.00x is valildo, 0.000x is not valid). *
+ * Answer: 'OK;' for each correct command, 'ERROR <wrong sequence> [(Port = <portName>, Pin = <pinNumber>)] [<analogic input name>])\n\r' in any other case.
+ * N.B .: the commands are shown using the following series of expansions:
+ * <s> to indicate a numeric or alphanumeric value with meaning 's'; [x-y] to indicate a range of integers from 'x' to 'y';
+  [a, b, c, ....] to indicate a series of possible strings (alphanumeric) 'a', 'b', 'c', ...; * and + as in Standards RE.
+ *
+ * @author Lorenzo Sola
+ * @date 18/12/2017
+ * @email lorenzo.sola@alice.it
+ */
+
+#define COMMAND_LIST_HELP "- \"DO P<portName [B-G]> *<pinNumber [0-15]> <value [01]>;\"\n\r- \"DP P<portName> *<pinNumber [0-15]> <time>;\"\n\r- \"DI P<portName> *<pinNumber [0-15]>;\"\n\r- \"AI <analogic input name (at the moment only [ADC_TEMP, ADC_VREF, ADC_VBAT])>;\"\n\r- \"H\" or \"?\" : print this help string on serial line.\n\r"
+
+#include "mbed.h"
+#include "USBSerial.h"
+// #include <regex.h>
+// #include <errno.h>
+#include <string>
+#include <unordered_map>
+#include <list>
+
+//Virtual serial port over USB
+USBSerial* serial;
+
+#define CMD_BUFF_LENGTH 16
+#define CHAR_BUFF_LENGTH 10
+#define OUT_BUFF_LEN 50
+#define NO_DATA_ACTION_TIME 4000 //(in ms)
+
+char cmdBuff[CMD_BUFF_LENGTH];
+int cmdBuffIdx;
+char portName; //(A,B,C,....)
+char adcInPinName[CHAR_BUFF_LENGTH];
+int pinNumber;
+int dValue;
+float tValue;
+char dataBuff[CHAR_BUFF_LENGTH];
+int dataBuffIdx;
+char outBuff[OUT_BUFF_LEN];
+char c;
+int tmpInt;
+enum states {q0, D, DO, DP, DI, OP_, Port, Pin, DValue, OP_OK, TValue, A, AI, analogInName, H} state = q0;
+enum operations {none, readPort, writePort, pulse, adcIn} operation = none;
+typedef unordered_map<std::string, PortOut> PortOutMap;
+typedef unordered_map<std::string, PortIn> PortInMap;
+typedef unordered_map<std::string, AnalogIn> AnalogInMap;
+
+void flushSerial()
+{
+    wait_ms(10);
+    int i;
+    do {
+        i = serial->available();
+        while (--i >= 0) {
+            serial->_getc();
+        }
+    } while (i > 0);
+}
+
+void writeLongStringOnSerial(const char* str)
+{
+    char* end = (char*)str + strlen(str);
+    int nWrite = 0;
+    while(str < end) {
+        if(serial->writeable()) {
+            nWrite = end - str;
+            if(nWrite > 16) nWrite = 16;
+            serial->writeBlock((uint8_t*) str, nWrite);
+            str += nWrite;
+        } else {
+            wait_ms(10);
+        }
+    }
+}
+
+class Flipper
+{
+private:
+    PortOut port;
+    int pin;
+    bool running;
+    Timeout timeout;
+    int inState;
+public:
+    Flipper(PortOut portOut, int pinNumber) : port(portOut), pin(pinNumber), running(0) {
+    }
+    void flipPin() {
+        inState = port.read();
+        inState ^= (1 << pin);
+        port.write(inState);
+        running = 0;
+        return;
+    }
+    void start(float time) {
+        flipPin();
+        running = 1;
+        timeout.attach(callback(this, &Flipper::flipPin), time);
+        return;
+    }
+    bool getRunning() {
+        return running;
+    }
+};
+typedef list<Flipper*> FlipperList;
+FlipperList flipperList;
+
+/**
+ * Pulizia di 1 BitFlipper scaduto tra quelli presenti in flipperList.
+ * @return true: 1 BitFlipper eliminato dalla lista; false: nessun BitFlipper scaduto presente in lista.
+ */
+inline void DropExpiredBitFlipper()
+{
+    //Pulizia BitFlipper con timer scaduto.
+    FlipperList::iterator fvI = flipperList.begin();
+//  if(fvI == flipperList.end()) serial->writeBlock((uint8_t*)"None BitFLipper in vector.\n\r", 28);
+    //Il ciclo deve essere interrotto ad ogni cancellazione perchè la posizione dopo una cancellazione non è più valida e prima o poi l'informazione posizionale dell'iteratore arriva ad elementi cancellati.
+    while(fvI != flipperList.end()) {
+        if(! (*fvI)->getRunning()) {
+//          serial->writeBlock((uint8_t*)"Erasing BitFlipper.\n\r", 21);
+            delete *fvI; //Non so perchè ma questa fa vatta per forza prima di quella dopo altrimenti crasha.
+            flipperList.erase(fvI);
+//          serial->writeBlock((uint8_t*)"BitFlipper Erased.\n\r", 20);
+            return;
+        }
+        fvI++;
+    }
+    return;
+}
+
+void writeErrorMessage()
+{
+    string errorString("\r\nERROR: \"");
+    errorString += cmdBuff;
+    //Per comporre il messaggio utlizzo appositamente uno string per stressare appena la CPU (praticamente di niente).
+    switch(operation) {
+        case readPort:
+        case writePort:
+        case pulse:
+            errorString += "\" (Port=";
+            errorString += portName;
+            errorString += ", Pin=";
+            errorString += (char)(pinNumber + '0');
+            errorString += ")\n\r";
+            break;
+        case adcIn:
+            errorString += "\" (ADC \"";
+            errorString += adcInPinName;
+            errorString += "\")\n\r";
+            break;
+        default:
+            errorString += "\"\n\r";
+    }
+    serial->writeBlock((uint8_t*)errorString.c_str(), errorString.length());
+//  memset(cmdBuff, 0, CMD_BUFF_LENGTH);
+    flushSerial();
+}
+
+void resetState()
+{
+    portName = '/';
+    pinNumber = -1;
+    dValue = -1;
+    state = q0;
+    memset(dataBuff, 0, CHAR_BUFF_LENGTH);
+    memset(cmdBuff, 0, CMD_BUFF_LENGTH);
+    dataBuffIdx = 0;
+    cmdBuffIdx = 0;
+    operation = none;
+}
+
+#define FAULT { \
+    writeErrorMessage(); \
+    resetState(); \
+}
+
+int main(void)
+{
+    serial = new USBSerial(0x1f00, 0x2012, 0x0001, false);
+    serial->printf("I am a virtual serial port.\n\r");
+    cmdBuffIdx = 0;
+    memset(cmdBuff, 0, CMD_BUFF_LENGTH);
+    dataBuffIdx = 0;
+    memset(dataBuff, 0, CHAR_BUFF_LENGTH);
+    /*********************Lezione su "initializer_list"********************/
+    //PortOutMap portMap(initializer_list<PortOutMap::value_type>({PortOutMap::value_type("pa", PortOut(PortB))}));
+
+    //Oppure (che è lo stesso per via del fatto che il compilatore interpreta argomenti di tipo "array" come "initializer_list" inizialittata con array):
+    //PortOutMap portMap({PortOutMap::value_type("pa", PortOut(PortB))});
+
+    //Oppure (per lo stesso motivo sopra applicato ricorsivamente fino a PortOutMap:value_type anch'esso inizializzabile con un "initializer_list"):
+    //PortOutMap portMap({{"pa", PortOut(PortB)}});
+    /*********************Fine lezione su "initializer_list"********************/
+
+    //Ed inizializzando con tutte le porte:
+    PortOutMap dOutPortMap( {{"PB", PortOut(PortB)}, {"PC", PortOut(PortC)}, {"PD", PortOut(PortD)}, {"PE", PortOut(PortE)}, {"PF", PortOut(PortF)}, {"PG", PortOut(PortG)}});
+    PortInMap dInPortMap( {{"PA", PortIn(PortA,0x3F)}}); //PortIn(PortA,0x18)
+    AnalogInMap adcInMap( {{"ADC_TEMP", AnalogIn(ADC_TEMP)}, {"ADC_VREF", AnalogIn(ADC_VREF)}, {"ADC_VBAT", AnalogIn(ADC_VBAT)}});
+
+    //Timer utile per eseguire operazioni specifiche dopo un certo tempo di assenza di dati in arrivo (come p.e. azzeramento del buffer e reset dello stato).
+    Timer serialEmptyTime;
+
+//  regex_t compiledRe;
+//  int errorNumber;
+//  if((errorNumber == regcomp(&compiledRe, "[DA][GS]ET P[ABCDEFGH] [01]", REG_EXTENDED)) != 0) {
+//      string errorMessage("ERROR: ");
+//      errorMessage+=strerror(errorNumber);
+//      serial->writeBlock(errorMessage.c_str(),errorMessage.length());
+//  }
+
+    int inState;
+    bool inputSkip = false;//Posta a true fa saltare l'attesa di input da seriale mantenendo il vecchio carattere come input dell'automa.
+    flushSerial();
+    serialEmptyTime.start();
+
+    while (1) {
+        if (serial->readable() || inputSkip) {
+            serialEmptyTime.reset();
+            if (cmdBuffIdx >= CMD_BUFF_LENGTH) { //Comando troppo lingo per essere valido: evito di sforare il buffer.
+                cmdBuffIdx = 0;
+                state = q0;
+                inputSkip = false;
+                writeErrorMessage();
+            } else {
+                if(!inputSkip) {
+                    c = serial->getc();
+                    cmdBuff[cmdBuffIdx++] = c;
+                } else inputSkip = false;
+                switch (state) {
+                    case q0:
+//                      serial->printf("State q0\r\n");
+                        switch (c) {
+                            case 'D':
+                                state = D;
+                                break;
+                            case ' ':
+                            case '\r':
+                            case '\n':
+                            case ';':
+                                cmdBuffIdx = 0;
+                                break;
+                            case 'A':
+                                state = A;
+                                break;
+                            case 'H':
+                            case '?':
+                                state = H;
+                                inputSkip = true;
+                                break;
+                            default:
+                                FAULT;
+                        }
+                        break;
+                    case D:
+//                      serial->printf("State D\r\n");
+                        switch(c) {
+                            case 'O':
+                                state = DO;
+                                break;
+                            case 'P':
+                                state = DP;
+                                break;
+                            case 'I':
+                                state = DI;
+                                break;
+                            default:
+                                FAULT;
+                        }
+                        break;
+                    case DP:
+                        if (c == ' ') {
+                            state = OP_;
+                            operation = pulse;
+                        } else FAULT;
+                        break;
+                    case DO:
+                        if (c == ' ') {
+                            state = OP_;
+                            operation = writePort;
+                        } else FAULT;
+                        break;
+                    case DI:
+                        if (c == ' ') {
+                            state = OP_;
+                            operation = readPort;
+                        } else FAULT;
+                        break;
+                    case OP_:
+                        if (c == 'P') {
+                            state = Port;
+                        } else FAULT;
+                        break;
+                    case Port: {
+                        portName = c;
+                        if (portName >= 'A' && portName <= 'H') {
+                            switch (operation) {
+                                case writePort:
+                                    state = Pin;
+                                    break;
+                                case readPort:
+                                    state = Pin;
+                                    break;
+                                case pulse:
+                                    state = Pin;
+                                    break;
+                                default:
+                                    FAULT;
+                            }
+                        } else {
+                            FAULT;
+                        }
+                        break;
+                    }
+//                  case OP_Pn: {
+//                      serial->printf("State OP_Pn\r\n");
+//                      if (c == ' ') {
+//                          state = Pin;
+//                      }
+//                      else FAULT;
+//                      break;
+//                  }
+                    case Pin: {
+                        switch (c) {
+                            case ' ': {
+                                if(dataBuffIdx == 0) break;
+                            }
+                            case ';': {
+                                if (dataBuffIdx == 0) {
+                                    FAULT;
+                                } else {
+                                    pinNumber = atoi(dataBuff);
+                                    //DEBUG
+//                                  serial->printf("\n\rDEBUG: pinNumber=%d\n\r", pinNumber);
+                                    //------
+                                    memset(dataBuff, 0, CHAR_BUFF_LENGTH);
+                                    dataBuffIdx = 0;
+                                    //***Controllo valore***
+                                    //***Fine controllo valore***
+                                    if (pinNumber < 0 || pinNumber > 15) {
+                                        FAULT;
+                                        break;
+                                    }
+                                    switch (operation) {
+                                        case writePort:
+                                            state = DValue;
+                                            break;
+                                        case readPort:
+                                            state = OP_OK;
+                                            inputSkip = true;
+                                            break;
+                                        case pulse:
+                                            state = TValue;
+                                            break;
+                                        default:
+                                            FAULT;
+                                    }
+                                }
+                                break;
+                            }
+                            default: {
+                                if (dataBuffIdx >= 2) {
+                                    FAULT;
+                                } else {
+                                    dataBuff[dataBuffIdx++] = c;
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case DValue: {
+//                      serial->printf("State S\n\r");
+                        switch (c) {
+                            case ' ':
+                                break;
+                            default:
+                                dValue = c - '0';
+                                if ((dValue == 0 || dValue == 1) && operation == writePort) state = OP_OK;
+                                else FAULT;
+                        }
+                        break;
+                    }
+                    case TValue: {
+                        switch (c) {
+                            case ' ': {
+                                if(dataBuffIdx == 0) break;
+                            }
+                            case ';': {
+                                if (dataBuffIdx == 0) {
+                                    FAULT;
+                                } else {
+                                    tValue = atof(dataBuff);
+                                    //DEBUG
+//                                  serial->printf("\n\rDEBUG: pinNumber=%d\n\r", pinNumber);
+                                    //------
+                                    memset(dataBuff, 0, CHAR_BUFF_LENGTH);
+                                    dataBuffIdx = 0;
+                                    //***Controllo valore***
+                                    //***Fine controllo valore***
+                                    switch (operation) {
+                                        case pulse:
+                                            state = OP_OK;
+                                            inputSkip = true;
+                                            break;
+                                        default:
+                                            FAULT;
+                                    }
+                                }
+                                break;
+                            }
+                            default: {
+                                if (dataBuffIdx >= 6) {
+                                    FAULT;
+                                } else {
+                                    dataBuff[dataBuffIdx++] = c;
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case OP_OK: {
+                        switch (c) {
+                            case ';': {
+                                switch (operation) {
+                                    case writePort: {
+                                        PortOutMap::iterator pi;
+                                        if ((pi = dOutPortMap.find(string("P") + portName)) != dOutPortMap.end()) {
+                                            inState = pi->second.read();
+                                            if (dValue == 1) inState |= (1 << pinNumber);
+                                            else inState &= ~(1 << pinNumber);
+                                            pi->second.write(inState);
+//                                          Composizione messaggio di debug. Con il "printf" funziona solo con minicom per cui uso la writeBlock.
+//                                          //serial->printf("DEBUG: setting port %c, pin %d, at value %d\r\n", portName, pinNumber, dValue);
+//                                          strcpy(outBuff, "DEBUG: setting port ");
+//                                          strcat(outBuff, &portName);
+//                                          strcat(outBuff, ", pin ");
+//                                          pinNumber += '0';
+//                                          strcat(outBuff, (char*)&(pinNumber));
+//                                          strcat(outBuff, ", at value ");
+//                                          dValue += '0';
+//                                          strcat(outBuff, (char*)&dValue);
+                                            serial->writeBlock((uint8_t*)"OK;", 3);
+                                        } else {
+//                                          serial->printf("Digital Output not available on port \"%c\"\r\n", portName);
+                                            snprintf(outBuff, OUT_BUFF_LEN, "Digital Output not available on port \"%c\";", portName);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        }
+                                        break;
+                                    }
+                                    case readPort: {
+                                        PortInMap::iterator pi;
+                                        if ((pi = dInPortMap.find(string("P") + portName)) != dInPortMap.end()) {
+                                            inState = pi->second.read();
+                                            inState &= (1 << pinNumber);
+                                            inState = (inState ? 1 : 0);
+                                            snprintf(outBuff, OUT_BUFF_LEN, "%i;", inState);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        } else {
+                                            snprintf(outBuff, OUT_BUFF_LEN, "Digital Input not available on port \"%c\";", portName);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        }
+                                        break;
+                                    }
+                                    case pulse: {
+                                        PortOutMap::iterator pi;
+                                        if ((pi = dOutPortMap.find(string("P") + portName)) != dOutPortMap.end()) {
+//                                          DropExpiredBitFlipper();
+                                            Flipper* f = new Flipper(pi->second, pinNumber);
+                                            f->start(tValue);
+                                            flipperList.push_back(f);
+                                            serial->writeBlock((uint8_t*)"OK;", 3);
+                                        } else {
+//                                          serial->printf("Digital Output not available on port \"%c\"\r\n", portName);
+                                            snprintf(outBuff, OUT_BUFF_LEN, "Digital Output not available on port \"%c\";", portName);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        }
+                                        break;
+                                    }
+                                    case adcIn: {
+                                        AnalogInMap::iterator pi;
+                                        if ((pi = adcInMap.find(string(adcInPinName))) != adcInMap.end()) {
+//                                          DropExpiredBitFlipper();
+                                            inState = pi->second.read_u16();
+                                            snprintf(outBuff, OUT_BUFF_LEN, "%i;", inState);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        } else {
+//                                          serial->printf("Digital Output not available on port \"%c\"\r\n", portName);
+                                            snprintf(outBuff, OUT_BUFF_LEN, "ADC Input \"%s\" not available;", adcInPinName);
+                                            serial->writeBlock((uint8_t*)outBuff, strlen(outBuff));
+                                        }
+                                        break;
+                                    }
+                                    default:
+                                        FAULT;
+                                }
+                                resetState();
+                                break;
+                            }
+                            case ' ':
+                                break;
+                            default:
+                                FAULT;
+                        }
+                        break;
+                    }
+                    case A:
+                        switch (c) {
+                            case 'I':
+                                state = AI;
+                                break;
+                            default:
+                                FAULT;
+                        }
+                        break;
+                    case AI:
+                        switch (c) {
+                            case ' ': {
+                                state = analogInName;
+                                operation = adcIn;
+                                break;
+                                default:
+                                    FAULT;
+                                }
+                        }
+                        break;
+                    case analogInName:
+                        switch (c) {
+                            case ' ': {
+                                if(dataBuffIdx == 0) break;
+                            }
+                            case ';': {
+                                if (dataBuffIdx == 0) {
+                                    FAULT;
+                                } else {
+                                    strncpy(adcInPinName, dataBuff, CHAR_BUFF_LENGTH); //Si poteva anche usare direttamente adcInPinName dentro all'assegnamento ciclico ma così è "uniforme" (in futuro si potrebbe fare una macro o funzione per questo genere di operazione).
+                                    //DEBUG
+//                                  serial->printf("\n\rDEBUG: adcInPinName=%s\n\r", adcInPinName);
+                                    //------
+                                    memset(dataBuff, 0, CHAR_BUFF_LENGTH);
+                                    dataBuffIdx = 0;
+                                    //***Controllo valore***
+                                    //***Fine controllo valore***
+                                    switch (operation) {
+                                        case adcIn:
+                                            state = OP_OK;
+                                            inputSkip = true;
+                                            break;
+                                        default:
+                                            FAULT;
+                                    }
+                                }
+                                break;
+                            }
+                            default: {
+                                if (dataBuffIdx >= CHAR_BUFF_LENGTH-1) {
+                                    FAULT;
+                                } else {
+                                    dataBuff[dataBuffIdx++] = c;
+                                }
+                            }
+                        }
+                        break;
+                    case H:
+                        serial->writeBlock((uint8_t*)"\n\rCommand Help:\n\r", 17);
+                        writeLongStringOnSerial(COMMAND_LIST_HELP);
+                        resetState();
+                        flushSerial();
+                        break;
+                }
+            }
+        } else {
+            if (serialEmptyTime.read_ms() > NO_DATA_ACTION_TIME) {
+                serialEmptyTime.reset();
+                resetState();
+                memset(cmdBuff, 0, CMD_BUFF_LENGTH);
+                cmdBuffIdx = 0;
+                //serial->writeBlock((uint8_t*)"DEBUG: timeout", 14);
+            }
+            DropExpiredBitFlipper();
+        }
+    }
+    delete serial;
+}