![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
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.
src/main.cpp
- Committer:
- lsola
- Date:
- 2017-12-19
- Revision:
- 1:cae05f3e5d56
- Parent:
- 0:2c26b4ba7cf3
- Child:
- 3:a425b8b0a4c2
File content as of revision 1:cae05f3e5d56:
/** * 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]>;'. * -Switch Pulse: combo '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 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. * -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; }