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
Diff: main.cpp
- Revision:
- 0:5d5265391846
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Mon Aug 13 07:05:57 2012 +0000 @@ -0,0 +1,452 @@ +// 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) + } +}