Flylib.com

Books Software

 
 
 

Interfacing to the PC Serial Port

Interfacing to the PC Serial Port

In almost all cases, when interfacing to the serial port of a PC, the tools that come with the PC are adequate. There are cases, however, when HyperTerminal isnt sufficient. For example, if you need to observe binary data on a serial line or if you need to implement a custom serial protocol on the target, you will find it quite handy to be able to manipulate the UART on the PC directly. Listing A.3 is a base for a home-grown serial port interface that you can use in place of the default Windows tools when necessary.

Listing A.3: Base for a Home-Grown Serial Port Interface.

image from book
HANDLE
comOpen(int portnum, int baud)
{
    DCB dcb;
    HANDLE hCom;
    char portname[16];

    sprintf(portname,"COM%d",portnum);
    hCom = CreateFile(portname,
        GENERIC_READ  GENERIC_WRITE,   // Open for read/write.
        0,                              // Sharing flags.
        NULL,                           // Security attributes.
        OPEN_EXISTING,                  // Required for serial port.
        0,                              // File attributes (NA).
        NULL);                          // Required for serial port.

    if (hCom == INVALID_HANDLE_VALUE) {
        ShowLastError("comOpen() CreatFile()");
        return(INVALID_HANDLE_VALUE);
    }

    // Fill in the DCB...
    if (!GetCommState(hCom,&dcb)) {
        ShowLastError("comOpen() GetCommState()");
        return(INVALID_HANDLE_VALUE);
    }
    dcb.BaudRate = baud;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    dcb.fDtrControl = DTR_CONTROL_ENABLE;
    dcb.fDsrSensitivity = FALSE;
    dcb.fOutX = FALSE;
    dcb.fInX = FALSE;
    dcb.fNull = FALSE;
    if (!SetCommState(hCom,&dcb)) {
        ShowLastError("comOpen() SetCommState()");
        return(INVALID_HANDLE_VALUE);
    }

    return(hCom);
}

void
comClose(HANDLE hCom)
{
    CloseHandle(hCom);
}

int
comRead(HANDLE hCom,char *buf,int count)
{
    DWORD   bytesread;
    DWORD   tot;

    tot = (DWORD)count;
    while(tot) {
        if (ReadFile(hCom,buf,(DWORD)tot,
            &bytesread,NULL) != TRUE) {
            ShowClearCommError(hCom);
            ShowLastError("comread ReadFile()");
            return(-1);
        }
        tot -= bytesread;
        buf += bytesread;
    }
    return(count);
}

int
comWrite(HANDLE hCom, char *buffer,int count)
{
    DWORD   byteswritten;
    
    if (WriteFile(hCom,buffer,(DWORD)count,
        &byteswritten,NULL) != TRUE)
            return(-1);
    return((int)byteswritten);
}
image from book
 

The code in Listing A.3 depends on the Windows services: CreateFile() , GetCommState() , SetCommState() , ReadFile() , and WriteFile() . For details on these system calls, refer to the Visual C++ CD documentation. The comOpen() function is a wrapper for CreateFile() , GetCommState() , and SetCommState() to open a com port that creates a convenient hook to configure baud, parity, and other serial-port parameters. The comRead() and comWrite() functions create convenient wrappers for the ReadFile() and WriteFile() system calls, which are used to transmit to and receive from the serial port.

PC-Based UDP Transactions: moncmd

MicroMonitor, when built on a target with Ethernet capability, includes a small server that processes incoming packets received on UDP port 777. The server simply takes the incoming UDP packet and assumes the content of the packet is a command destined for the monitors CLI. The server passes the whole string to the function docommand() and sets a flag within the monitor. This flag tells putchar to build UDP packets (one line per packet) to be sent back to the client that issued the command. When the flag is set, the command results are sent to the target console (if any) and to the remote UDP port that issued the command. When the final line of output is complete, the monitor clears the flag and issues one last packet containing only one NULL byte to signal the remote client that the response to the command is complete.

The moncmd tool (monitor command) is a specialized UDP client that runs on a PC. It is a simple program that allows a user to talk remotely to a target system running MicroMonitor. Listing A.4 demonstrates the basic functionality needed to send and receive on a UDP socket using a Win32-based host. Note that Listing A.4 is a very basic implementation meant only to illustrate the use of socket() , sendto () , and recvfrom() . (A complete implementation is on the CD.)

Listing A.4: do_moncmd().

image from book
/* do_moncmd():
 *  Open a socket and send the command to the specified port of the
 *  specified host.  Wait for a response if necessary.
 *
 *  hostname:
 *      Character string IP address
 *  command_to_monitor:
 *      Character string command destined for target monitor.
 *  portnum:
 *      Port number that the UDP packet is to be sent to.
 */
int
do_moncmd(char *hostname, char *command_to_monitor, short portnum)
{
    int i, lasterr;
    int msglen;
    ulong   inaddr;
    struct  hostent *hp, host_info;
    char    rcvmsg[1024];
    WSADATA WsaData;
    DWORD   tid;
    HANDLE  tHandle;

   if (WSAStartup (0x0101, &WsaData) == SOCKET_ERROR)
        err("WSAStartup Failed");

    targetHost = hostname;

    /* Build the UDP destination address:
     * Accept target name as string or internet dotted-decimal address.
     */
    memset((char *)&targetAddr,0,sizeof(struct sockaddr));
    if ((inaddr = inet_addr(targetHost)) != INADDR_NONE) {
        memcpy((char *)&targetAddr.sin_addr,(char *)&inaddr,sizeof(inaddr));
        host_info.h_name = NULL;
    }
    else {
        hp = gethostbyname(targetHost);
        if (hp == NULL)
            err("gethostbyname failed");
        host_info = *hp;
        memcpy((char *)&targetAddr.sin_addr,hp->h_addr,hp->h_length);
    }
    targetAddr.sin_family = AF_INET;
    targetAddr.sin_port = htons(portnum);

    /* Open the socket: 
     */
    socketFd = socket(AF_INET,SOCK_DGRAM,0);

    if (socketFd == INVALID_SOCKET)
        err("socket failed");

    /* Send the command string to the target:
     */
    msgString = command_to_monitor;
    if (sendto(socketFd,msgString,(int)strlen(msgString)+1,0,
        (struct sockaddr *)&targetAddr,sizeof(targetAddr)) < 0) {
        close(socketFd);
        err("sendto failed");
    }

    /* Now wait for the response:
     */
    while(1) {
        int j;

        /* Wait for incoming message: */
        msglen = sizeof(struct sockaddr);
        i = recvfrom(socketFd,rcvmsg,sizeof(rcvmsg),0,
            (struct sockaddr *)&targetAddr,&msglen);

        if (i == 0) {
            fprintf(stderr,"Connection closed\n");
            close(socketFd);
            exit(EXIT_ERROR);
        }

        /* If size is 1 and 1st byte is 0 assume that's the target */
        /* saying "I'm done". */
        if ((i==1) && (rcvmsg[0] == 0))
            break;

        /* Print the received message: */
        for(j=0;j<i;j++)
            putchar(rcvmsg[j]);
        fflush(stdout);
    }

    close(socketFd);
    return(EXIT_SUCCESS);
}
image from book
 

Okay, Im gonna be honest here, I dont know what WSAStartup() actually does! And yes, Im OK with that! Like the sneaker commercial says, "just do it!"

The do_moncmd() function processes the IP address or the hostname provided on the command line. The function then opens a socket with SOCK_DGRAM , indicating that the socket is for UDP. After the socket is opened, the code can use the functions sendto() and recvfrom() to send and receive UDP packets. The call to sendto() transfers the string on the command line to the server task on the remote target running MicroMonitor. Finally, the while() loop processes the response. Each line received is printed to the console. To support the ability to receive a multiple-line response but still know when the final line has been received, the server in the monitor terminates the message with a single 1-byte packet containing a NULL . The remote moncmd tests for this packet and exits when appropriate.