This application will buffer and send lines from up to three serial devices and USB host when connected to via TCP, with telnet or netcat. Written, 02/10/2011-2/14/2011 by Graham Cantin & Special Guest Appearance from Sasha Jevtic (mostly Sasha)
Dependencies: EthernetNetIf MODDMA MODGPS MODSERIAL NTPClient mbed
main.cpp
- Committer:
- kamilion
- Date:
- 2012-08-13
- Revision:
- 0:5d5265391846
File content as of revision 0:5d5265391846:
// Telnet UART MUX server // This application will buffer and send lines from up to // three serial devices and USB host when connected to via TCP. // Written, 02/10/2011-2/14/2011 by Graham Cantin and // Special Guest Appearance from Sasha Jevtic (Patron Diety of Queues) /* "Have you looked on the underside of the mBed?" There's a chip marked "mBed Interface" which is an 8-bit microcontroller in it's own right and an AT45DB161 16Mbit flash device. It's the "backend" of the mBed system you are seeing, not the target LPC1768 device on the top side of the mBed. This interface exposes the flash device as a FAT partition over USB, and also handles resetting the LPC1768 when a serial break occurs. */ // 02/14/2011: Added a hard fault handler. Please improve if you've got a bit of time. // ------ Defines ------ // DEBUG LOGGING #define DEBUG // DHCP OR STATIC IP? #define DHCP // What port do we listen on? #define TCP_LISTENING_PORT 3001 // 9600, 14400, 19200, 38400, 57600, 115200, 230400, 460800 (too fast), 921600 (way too fast) #define HOST_BAUD 115200 #define MODSERIAL_DEFAULT_RX_BUFFER_SIZE 2048 #define MODSERIAL_DEFAULT_TX_BUFFER_SIZE 384 #define MESSAGE_BUFFER_SIZE 256 #define TS_PAD 10 #define TX_CHUNK 30 #define MAX_LINES_PER_ITER 10 #define Q_ALERT_THRESH 70 // ------ Includes ------ #include "mbed.h" #include "line_util.h" #include "EthernetNetIf.h" #include "TCPSocket.h" #include "NTPClient.h" #include "MODDMA.h" #include "MODSERIAL.h" #include "GPS.h" // ------ Constants ------ const char BannerText[] = "**** Software Build Date : "__DATE__" "__TIME__" ****\r\n"; const char REC_SEP[] = "\r\n"; // ------ Reset & Crash ------ extern "C" void mbed_reset(); // Ask for soft-reset (like pressing the button) extern "C" void NVIC_SystemReset(); // Force a hard reset by calling the low level raw function. // Handles a hard fault with a reset. SEE: http://mbed.org/forum/mbed/post/2843/ extern "C" void HardFault_Handler() { printf("HARD FAULT!\r\n"); NVIC_SystemReset(); } // SEE: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337g/Babcefea.html // Could be handled better. // MBED WATCHDOGS: http://mbed.org/forum/mbed/post/3276/ // ------ Timers ----- // Note that timers are based on 32-bit int microsecond (us) counters, // so can only time up to a maximum of 2^31-1 microseconds i.e. 30 minutes. Timer highres; // for reading // ------ LEDS ----- PwmOut led1(LED1, "led1"); PwmOut led2(LED2, "led2"); PwmOut led3(LED3, "led3"); PwmOut led4(LED4, "led4"); // ------ Buttons ----- // Define some interrupts for buttons. InterruptIn P21(p21); InterruptIn P22(p22); InterruptIn P23(p23); InterruptIn P24(p24); #define BUTTON_MESSAGE_SEND(x,y) \ printf("$MBEDE,%d,BUTTON,%02d,%d*FF\r\n",time(NULL),x,y); void pin21Rise(void) { BUTTON_MESSAGE_SEND(21, 1); } void pin21Fall(void) { BUTTON_MESSAGE_SEND(21, 0); } void pin22Rise(void) { BUTTON_MESSAGE_SEND(22, 1); } void pin22Fall(void) { BUTTON_MESSAGE_SEND(22, 0); } void pin23Rise(void) { BUTTON_MESSAGE_SEND(23, 1); } void pin23Fall(void) { BUTTON_MESSAGE_SEND(23, 0); } void pin24Rise(void) { BUTTON_MESSAGE_SEND(24, 1); } void pin24Fall(void) { BUTTON_MESSAGE_SEND(24, 0); } void buttonSetup() { // Enable pullup resistors on pins. P21.mode(PullUp); P22.mode(PullUp); P23.mode(PullUp); P24.mode(PullUp); // Fix Mbed library bug, see http://mbed.org/forum/bugs-suggestions/topic/1498 // Prevents pullup interrupts from firing once on below attachment during startup. LPC_GPIOINT->IO2IntClr = (1UL << 5) | (1UL << 4) | (1UL << 3) | (1UL << 2); // Attach InterruptIn pin callbacks. P21.rise(&pin21Rise); P21.fall(&pin21Fall); P22.rise(&pin22Rise); P22.fall(&pin22Fall); P23.rise(&pin23Rise); P23.fall(&pin23Fall); P24.rise(&pin24Rise); P24.fall(&pin24Fall); } // ------ Ethernet ----- #ifdef DHCP EthernetNetIf eth; // Use DHCP from router #else EthernetNetIf eth( IpAddr(10,4,0,212), //IP Address IpAddr(255,255,255,0), //Network Mask IpAddr(10,4,0,1), //Gateway IpAddr(10,0,0,7) //DNS ); // Define a static IP to run with for early crossover cable debugging. #endif NTPClient ntp; // Object for the ntpclient void rtcSetup() { Host server(IpAddr(), 123, "0.us.pool.ntp.org"); ntp.setTime(server); time_t ctTime; ctTime = time(NULL); printf("$MBEDE,%d,TIME_SET_NTP,%s",time(NULL), ctime(&ctTime)); } Host client; // a Host (IP address) object named client; the one connecting to us. TCPSocket ListeningSock; // Actual listening socket TCPSocket* pConnectedSock; // pointer for ConnectedSock TCPSocketErr err; // Error slot for TCP Socket bool clientExists = 0; void onConnectedTCPSocketEvent(TCPSocketEvent e) { switch(e) { case TCPSOCKET_WRITEABLE: // printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_SENDING_DATA*FF\r\n",time(NULL)); led1 = 1.0; // Blink an xmit led1 = 0.10; break; case TCPSOCKET_READABLE: printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_READABLE*FF\r\n",time(NULL)); led2 = 1.0; // Blink a recv led2 = 0.10; break; // The following cases generally should not happen often, but are explicitly covered anyway case TCPSOCKET_CONTIMEOUT: case TCPSOCKET_CONRST: case TCPSOCKET_CONABRT: case TCPSOCKET_ERROR: case TCPSOCKET_DISCONNECTED: printf("$MBEDE,%d,IP,CONNECTED,TCP_SOCKET_DISCONNECTED*FF\r\n",time(NULL)); pConnectedSock->close(); clientExists = 0; SLine_put_control(false); SLine_clear(); led4 = 0.0; // Shut off LED4 (Connected) led1 = 0.0; // Shut off LED1 (Xmit) break; default: break; } } void onListeningTCPSocketEvent(TCPSocketEvent e) { switch(e) { case TCPSOCKET_ACCEPT: printf("$MBEDE,%d,IP,LISTENING,TCP_SOCKET_ACCEPTED*FF\r\n",time(NULL)); // Accepts connection from client and gets connected socket. err=ListeningSock.accept(&client, &pConnectedSock); if (err) { printf("$MBEDE,%d,IP,onListeningTcpSocketEvent,Could not accept connection*FF\r\n",time(NULL)); return; //Error in accept, discard connection (nmap/netcat -z portscanning us?) } // Setup the new socket events pConnectedSock->setOnEvent(&onConnectedTCPSocketEvent); // We can find out where the connection is coming by looking at the // Host parameter of the accept() method IpAddr clientIp = client.getIp(); printf("$MBEDE,%d,IP,LISTENING,TCP_CONNECT_FROM,%d.%d.%d.%d*FF\r\n",time(NULL), clientIp[0], clientIp[1], clientIp[2], clientIp[3]); clientExists = 1; // Mark ourselves connected. SLine_clear(); SLine_put_control(true); led4 = 0.10; // Turn on LED4 (Connected) break; // The following cases generally should not happen often, but are explicitly covered anyway case TCPSOCKET_CONRST: case TCPSOCKET_CONTIMEOUT: case TCPSOCKET_CONABRT: case TCPSOCKET_ERROR: case TCPSOCKET_DISCONNECTED: // Close the socket (nmap/netcat -z portscanning us?) printf("$MBEDE,%d,IP,LISTENING,TCP_SOCKET_DISCONNECTED*FF\r\n",time(NULL)); ListeningSock.close(); clientExists = 0; SLine_put_control(false); SLine_clear(); led4 = 0.0; // Shut off LED4 (Connected) break; default: break; }; } // ------ Serial ----- // Set up the UARTs with MODSERIAL buffering MODSERIAL hostPort(USBTX, USBRX); MODSERIAL gpsPort(p13, p14); MODSERIAL imuPort(p9, p10); MODSERIAL wheelPort(p28, p27); void captureSerial(MODSERIAL* serial, LINE_T* line, LINE_SRC_T src) { memset(line->line, 0, LINE_MAX_LEN + 1); // Empty out buffer first line->source = src; // Enumerate source from line_util.h line->usec = highres.read_us(); // Read highres timer serial->move(line->line, LINE_MAX_LEN); // Move the buffer into the struct member line->len = strip_crlf(line->line); // Strip the line of endings of the struct member line->timestamp = time(NULL); // Read RTC time } void generateOutput(TCPSocket* socket, LINE_T* line) { char tx_buf[MESSAGE_BUFFER_SIZE]; unsigned int tx_length; // total bytes to be sent. int bytes_sent; // total bytes sent so far in function. int send_rc; // result of last send operation. unsigned int send_chunk; // amount attempted for tx operation. unsigned int consec_zeros; // consecutive socket send ops returning 0. if (line->len > 0) { // Though not strictly necessary, life is just easier if we preformat // the data we want to send by socket. tx_length = snprintf(tx_buf, MESSAGE_BUFFER_SIZE, "%s,%010u,%010u,%s%s", LINE_SRC_NAMES[line->source], line->timestamp, line->usec, line->line, REC_SEP); bytes_sent = 0; send_rc = 0; consec_zeros = 0; printf(tx_buf); // Traditional socket transmission APIs don't guarantee that // everything we ask to send will get sent. Details on the mbed // implementation are light, but it is wise to take this likelihood // into consideration. Socket buffer capacity is certainly not // infinite, and can fill for a variety of reasons. while ((send_rc >= 0) && ((tx_length - bytes_sent) > 0)) { // We'll go at it until we're out of stuff to send or we get an // error when we send (traditional socket transmission APIs // return -1 when encountering an error. send_chunk = ((tx_length - bytes_sent) > TX_CHUNK ? TX_CHUNK : tx_length - bytes_sent); // We use this notion of chunking to go easy on the network // stack, never trying to transmit a large amount of data // in a single call. send_rc = pConnectedSock->send(&(tx_buf[bytes_sent]), send_chunk); bytes_sent += (send_rc > 0 ? send_rc : 0); // If we try to send too much and not poll often enough, the // network stack will stop being able to accept data. So, we'll // poll here since this is a really intense area of transmission. Net::poll(); if (consec_zeros && (send_rc > 0)) { printf("### Recovery: First non-zero send after %06u tries " "(%04d/%04u). ###\r\n", consec_zeros, send_rc, send_chunk); printf("Serial data line queue utilization: %u/%u.\r\n", SLines_get_fill(), SLines_get_capacity()); consec_zeros = 0; } if (send_rc < send_chunk) { if (!consec_zeros) { printf("### Send result: %04d/%04u. ###\r\n", send_rc, send_chunk); printf("Serial data line queue utilization: %u/%u.\r\n", SLines_get_fill(), SLines_get_capacity()); } if (send_rc <= 0) { consec_zeros++; } } } } } // ------ Serial ISRs ----- void gpsMessageReceive(void) { LINE_T new_line; captureSerial(&gpsPort, &new_line, LINE_SRC_GPS); SLine_put(&new_line); } void imuMessageReceive(void) { LINE_T new_line; captureSerial(&imuPort, &new_line, LINE_SRC_IMU); SLine_put(&new_line); } void wheelMessageReceive(void) { LINE_T new_line; captureSerial(&wheelPort, &new_line, LINE_SRC_WHEEL); SLine_put(&new_line); } void hostMessageReceive(void) { LINE_T new_line; captureSerial(&hostPort, &new_line, LINE_SRC_HOST); SLine_put(&new_line); } void processSerialQueues() { unsigned int cur_fill; unsigned int lines_handled; cur_fill = SLines_get_fill(); lines_handled = 0; while ((cur_fill > 0) && (lines_handled < MAX_LINES_PER_ITER)) { // It's tempting to just process every line we have while we're in // here. However, since we're in a super-looped environment, network // I/O is completed via polling, and it seems prudent to not jam too // much down the stack between polling operations. if (((cur_fill * 100) / SLines_get_capacity()) >= Q_ALERT_THRESH) { printf("Serial data line queue utilization: %u/%u.\r\n", cur_fill, SLines_get_capacity()); } if (clientExists) { generateOutput(pConnectedSock, SLine_get()); } SLine_remove(); cur_fill = SLines_get_fill(); lines_handled++; } } void serialSetup() { // Set up the USB UART for host debugging hostPort.baud(HOST_BAUD); // Need a baud rate. We can go faster, but this is good. hostPort.attach(&hostMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive. hostPort.autoDetectChar('\r'); // Tell the handler to keep an eye for carrage returns. // Set up the GPS UART for incoming sentences gpsPort.baud(38400); // Need a baud rate. We can go faster, but this is good. gpsPort.attach(&gpsMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive. gpsPort.autoDetectChar('\n'); // Tell the handler to keep an eye for newlines. // Set up the IMU UART for incoming sentences imuPort.baud(57600); // Need a baud rate. We can go faster, but this is good. imuPort.attach(&imuMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive. imuPort.autoDetectChar('\n'); // Tell the handler to keep an eye for newlines. // Set up the Wheel UART for incoming sentences wheelPort.baud(57600); // Need a baud rate. We can go faster, but this is good. wheelPort.attach(&wheelMessageReceive, MODSERIAL::RxAutoDetect); // Attach a handler on receive. wheelPort.autoDetectChar('\r'); // Tell the handler to keep an eye for carrage returns. } int ethernetSetup() { // Setup ethernet hardware printf("\r\n$MBEDE,%d,IP,ETHERNET_DHCP_SLEEP_20*FF\r\n",time(NULL)); // Send some status wait(20); printf("\r\n$MBEDE,%d,IP,ETHERNET_DHCP*FF\r\n",time(NULL)); // Send some status EthernetErr ethErr = eth.setup(60000); // Initiate ethernet setup (DHCP or Static IP) if(ethErr) { printf("$MBEDE,%d,IP,ETHERNET_ERROR,%d*FF\r\n",time(NULL), ethErr); return -1; } IpAddr ip = eth.getIp(); // Say our IP address printf("$MBEDE,%d,IP,ADDRESS,%d.%d.%d.%d*FF\r\n",time(NULL), ip[0], ip[1], ip[2], ip[3]); led3 = 0.10; // Turn on LED3 (DHCP Success) led1 = 0.00; // Turn off LED1 (Xmit) led2 = 0.00; // Turn off LED2 (Recv) return 0; } void portSetup() { // Set the callbacks for Listening ListeningSock.setOnEvent(&onListeningTCPSocketEvent); // bind and listen on TCP err=ListeningSock.bind(Host(IpAddr(), TCP_LISTENING_PORT)); // Bind the port printf("$MBEDE,%d,IP,BINDING_PORT,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT); if(err) printf("$MBEDE,%d,IP,BINDING_ERROR,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT); err=ListeningSock.listen(); // Starts listening printf("$MBEDE,%d,IP,LISTENING_PORT,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT); if(err) printf("$MBEDE,%d,IP,LISTENING_ERROR,%i*FF\r\n",time(NULL), TCP_LISTENING_PORT); } // ------ MAINLOOP ----- int main() { led1 = 1.0; highres.start(); // Initiate highres timer ticking. led1 = 0.75; buttonSetup(); // Activate button ISRs led1 = 0.50; serialSetup(); // Activate Serial ISRs led1 = 0.10; printf(BannerText); led2 = 1.0; if(ethernetSetup()==-1) return -1; // 'quit' if we cannot setup ethernet. (return to bootloader/'reboot') led2 = 0.75; rtcSetup(); // Get time from network led2 = 0.50; portSetup(); // Open listens led2 = 0.10; while(true) { // infinite network poll loop Net::poll(); // Poll the network stack. processSerialQueues(); // Process queued messages (Seems fastest here) } }