Paso 2: Aprendizaje de la comunicación SERIAL
Antes de profundizar en el proceso de desarrollo, me gustaría ir sobre alguna lectura preliminar para entender lo que estamos tratando de hacer. Yo ya he compilado un tutorial simple de comunicación serial (segundo link), por lo que una vez que haya terminado, podemos empezar a desarrollar un programa totalmente funcional para nuestros propósitos.
MATERIALES DE LECTURA:
Vamos a empezar con crear 2 funciones simples, que le permitirá abrir y cerrar conexión UART.
Para esto necesita MS Visual C++, par de manos y el cerebro la cafeína infundido.
La inicialización del puerto COM es un proceso sencillo: primero creamos puerto configuración portDCB, que contiene todos los ajustes de comunicación, y luego asignamos el mango del puerto. Tenga en cuenta que puerto se inicializa con una llamada a función CreateFile() , y al igual que con los archivos convencionales podemos utilizar ReadFile() y WriteFile() para intercambiar datos.
Luego asignamos la nueva configuración con una llamada a función SetCommState() . Si en cualquier etapa de este proceso encontramos un error, vamos a imprimir el mensaje apropiado y devolverá FALSE.
De lo contrario, nos devuelva TRUE y como resultado de la ejecución de UART_Init(), variable de Puerto ahora apuntará a un controlador de puerto serie.
Con el fin de flexibilidad nos proporcionará el nombre del puerto COM y la velocidad en baudios como argumentos de esta función. Por defecto para ajustar a la longitud de la transmisión de 8 bits con 1 bit de parada. Paridad, corrección de errores y cualquier tipo de control de flujo están deshabilitadas por defecto.
/* * UART_Init() * Opens the com port with ID "portName" at baud rate "baud" * HANDLE *port becomes a pointer to an active COM port connection * Returns whether the connection is successful or not. */ BOOL UART_Init(HANDLE *port, LPCWSTR portName, DWORD baud) { DCB portDCB; // _DCB struct for serial configuration bool result = FALSE; // Return value COMMTIMEOUTS comTOUT; // Communication timeout *port = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL); // Try opening port communication if(*port==INVALID_HANDLE_VALUE) { wprintf(L"ERROR: Cannot open port %s\n",portName); return FALSE; } // NEW SETTINGS portDCB.DCBlength = sizeof(DCB);// Setup config length GetCommState(*port, &portDCB); // Get default port state portDCB.BaudRate = baud; // Set baud rate portDCB.fBinary = TRUE; // Enable Binary mode portDCB.fParity = FALSE; // Disable parity portDCB.fOutxCtsFlow = FALSE; // No CTS portDCB.fOutxDsrFlow = FALSE; // No DSR portDCB.fDtrControl = DTR_CONTROL_DISABLE; // No DTR portDCB.fDsrSensitivity = FALSE; // No DSR sensitivity portDCB.fTXContinueOnXoff = TRUE; // TX on XOFF portDCB.fOutX = FALSE; // No XON/XOFF portDCB.fInX = FALSE; // portDCB.fErrorChar = FALSE; // No error correction portDCB.fNull = FALSE; // Keep NULL values portDCB.fRtsControl = RTS_CONTROL_DISABLE; // Disable RTS portDCB.fAbortOnError = FALSE; // Disable abort-on-error portDCB.ByteSize = 8; // 8-bit frames portDCB.Parity = NOPARITY; // Parity: none portDCB.StopBits = ONESTOPBIT; // StopBits: 1 // Try reconfiguring COM port if (!SetCommState (*port, &portDCB)) { wprintf(L"ERROR: Cannot configure port %s\n",portName); return FALSE; } /// Communication timeout values result = GetCommTimeouts(*port, &comTOUT); comTOUT.ReadIntervalTimeout = 10; comTOUT.ReadTotalTimeoutMultiplier = 1; comTOUT.ReadTotalTimeoutConstant = 1; /// Set new timeout values result = SetCommTimeouts(*port, &comTOUT); return TRUE; }
El cierre de puerto COM es muy fácil. Todos tenemos que hacer es soltar el mango (línea 2) * Puerto puntero a NULL, para que accidentalmente no accede el mango viejo.
La función UART_Close() devuelve FALSE si estamos tratando de cerrar un identificador de puerto sin inicializar o previamente cerrada.
BOOL UART_Close(HANDLE *port) { if (*port == NULL) return FALSE; CloseHandle(*port); *port = NULL; return TRUE; }
Como ya has adivinado, el siguiente paso lógico será aplicar funciones para enviar y recibir mensajes UART. El momento clave de esta parte es que vamos a utilizar eventos de comunicación, descritas en el artículo MSDN mencionado anteriormente.
BOOL UART_Send(HANDLE port, char *Buffer) { DWORD bytesTransmitted; if(!WriteFile(port,Buffer, strlen(Buffer), &bytesTransmitted, NULL)) { DWORD Errors; COMSTAT Status; ClearCommError(port,&Errors,&Status); printf("ERROR: Unable to send data.\n"); return FALSE; } else { return TRUE; } }
Suponiendo que nuestro arduino puede ser ocupado en el momento de la transmisión y no podía proporcionar una respuesta adecuada, queremos esperar para que el evento EV_RXCHAR cada vez RX tiene los datos de entrada. Para solucionar este problema se creó una máscara de comunicaciones y esperar nuestro evento antes de leer el siguiente byte.
BOOL UART_Receive(HANDLE port, char *Buffer) { DWORD bytesTransmitted = 0; // Byte counter DWORD status = EV_RXCHAR; // transmission status mask memset(Buffer, 0, BUFFER_SIZE); // Clear input buffer SetCommMask (port, EV_RXCHAR); // Set up event mask WaitCommEvent(port, &status, 0); // Listen for RX event if(status & EV_RXCHAR) // If event occured { DWORD success=0; char c = 0; do { if(!ReadFile(port,&c, 1, &success, NULL)) // Read 1 char { // If error occured, print the message and exit DWORD Errors; COMSTAT Status; ClearCommError(port,&Errors,&Status); // Clear errors memset(Buffer, 0, BUFFER_SIZE); // Clear input buffer printf("ERROR: Unable to receive data.\n"); // Print error message return FALSE; } else { Buffer[bytesTransmitted]=c; // Add last character bytesTransmitted++; // Increase trans. counter } } while((success==1) && (c!='\n')); // do until the end of message } return TRUE; }
Estas cuatro funciones deben ser suficiente para manejar la comunicación UART básica entre Arduino y su PC.
Ahora, vamos a evaluar la funcionalidad de nuestro código con una simple prueba de loopback UART. Tenemos que terminar primero la función de _tmain() del programa:
int _tmain(int argc, _TCHAR* argv[]) { HANDLE port; char Buffer[BUFFER_SIZE] = "TEST MESSAGE\n"; // Unable to open? exit with code 1 if (!UART_Init(&port, L"COM8:", CBR_115200)) { system("PAUSE"); return 1; } // : continue execution else { // Here we send the string from buffer and print the response. // Our Arduino loopback should return the same string int msgs = 0; // reset # of messages while((port!=INVALID_HANDLE_VALUE) && (msgs<100)) // Send/Receive 100 messages { printf("Sending: %s\n", Buffer); UART_Send(port, Buffer); // Send data to UART port if(UART_Receive(port, Buffer)) // Receive data printf("Received: %s\n", Buffer); PurgeComm(port, PURGE_RXCLEAR | PURGE_TXCLEAR); // Flush RX and TX msgs++; // Increment # of messages } UART_Close(&port); // Close port } system("PAUSE"); return 0; }
Este código inicializa el puerto COM8, que es mi cable USB-UART (no te olvides de cambiar esa parte a su puerto #). Luego, envía 100 mensajes por UART e imprime original mensaje y respuesta. Aplicar el detector de eventos de comunicación anteriores realmente valió la pena al final. Si miramos este programa con cuidado, verás que sólo hemos utilizado sobre una docena de líneas de código eficaz para hacer que funcione!
Ahora, vamos a configurar nuestro Arduino como dispositivo de loopback de la UART. También implementamos una comunicación UART por eventos con el fin de poder hacer otras cosas mientras no transmite.
Abra el IDE de Arduino y utilizar este código como un ejemplo:
String buffer = ""; // a string to hold incoming data void setup() { buffer.reserve(255); // Reserve 255 chars Serial.begin(115200); // Initialize UART } void loop() { // NOP } // SerialEvent occurs every time we receive RX interrupt void serialEvent() { while (Serial.available()) { char c = (char)Serial.read(); // Read character buffer += c; // Add it to buffer // If end-of-line, reset buffer and send back the data if (c == '\n') { Serial.print(buffer); // Loopback buffer = ""; // Clear buffer } } }
Ahora puede subir el sketch de Arduino, compilar el proyecto de C++ y probarlo!