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

src/main.cpp

Committer:
lsola
Date:
2018-01-17
Revision:
3:a425b8b0a4c2
Parent:
1:cae05f3e5d56

File content as of revision 3:a425b8b0a4c2:

/**
 * 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, suggerimento come risposta a 'H' o '?', '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 un 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").
 * -Digital Output: command 'DO P<portName [B-G]> *<pinNumber [0-15]> <value [01]>;'.
 * -Digital Pulse: command 'DP P<portName> *<pinNumber [0-15]> <time>;'.
 *  Where <time> is a float value with a maximum accuracy of 10 ^ -3 Sec (eg: 0.00x is valildo, 0.000x is not valid). *
 *  From the moment the specified comand is given, the corresponding digital output is swhitched 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.
 * -Digital 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])>;'
 * -Request of a brief: 'H' or '?'.
 * Answer: 'OK;' for each correct command, command bried for brief request, 'ERROR <wrong sequence> [(Port = <portName>, Pin = <pinNumber>)] [<analogic input name>])\n\r' in any other case.
 * N.B .: 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;
}