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.
Diff: src/main.cpp
- 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; +}