Connect to Arduino Mega serial interface by C code

Hi all

I'm having trouble talking to an Arduino Mega using serial port commands on Windows. Identical code works fine to connect to an Arduino Duemilanove but doesn't allow connection to the serial port with the Mega. Also, I can access both cards from the Arduino IDE software, on both Linux and Windows.

I have serial port code that works fine on Linux for both Duemilanove and Mega. But I can't write code that works for the Mega on Windows, even though it works fine with the Duemilanove at this stage.

My only thought is that perhaps something is different in the way that the Mega's USB-to-serial hardware is set up, and perhaps, at least on Windows, I need to add some additional command (maybe on the Arduino, or maybe in my C code) to enable the interface to be correctly visible/accessible.

Any thoughts anyone? Anyone else had this problem?

Cheers
JP

Just to say I haven't had this problem. Is windows recognising the mega? It could be a problem with the drivers. Once that is established it just looks like a serial port. Of course the COM port number will be different for the different boards, have you changes that in your PC setup?

I would have thought that too, but the Arduino IDE is able to connect to the software in both cases... does the Arduino IDE use its own built-in drivers, or something like that? How can the serial connection work from within the IDE but fail in home-made C code?

How can the serial connection work from within the IDE but fail in home-made C code?

May I suggest a fault in the home-made C code. ::slight_smile:

Of course, but what would cause success with my code with the Duemilanove but failure with the same code when attempting to connect with the Mega ? Don't they both connect using the same serial protocol, communication settings, etc? I am finding that I don't even get past the first stop of opening the COM port!

I am finding that I don't even get past the first stop of opening the COM port!

That suggest to me that you are using the same COM port number for both boards and they will be different. As you say everything else is the same.

No, I'm changing the port numbers. I use the port names as indicated in the Arduino IDE. My error messages clearly indicate that the port names are being correctly conveyed through the code.

I use the port names as indicated in the Arduino IDE.

Have you got the monitor open in the Arduino IDE? That will stop anything else connecting to it.

Yes, I've closed the monitor. That's not it. Only thing I noticed so far was that the Device ID is slightly different, as shown in the Windows Device Manager.

The Mega is on which serial port?

On a Windows 7 machine, it was coming up as COM14. On a Windows XP machine, it was coming up as COM11. The port numbers are different from the ports that an Duemilanove comes up on. The port names come up the same every time, for a given board type.

Post the CreateFile call.

Complete serial port access code is following. Code is released as FOSS under the GPL3 license. The CreateFile call is

CreateFile(serialport, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

Following is the full coding listing. Firstly, serialport.c, written by me (with contributions from Ming Chin Pua) and based on SerialClass.cpp from Arduino.cc site.

#include <stdio.h>    /* Standard input/output definitions */
#include <stdlib.h> 
#include <string.h>   /* String function definitions */
#include <fcntl.h>    /* File control definitions */
#include <errno.h>    /* Error number definitions */

#ifdef _WIN32
# include <windows.h>
#else
# include <termios.h>  /* POSIX terminal control definitions */
# include <sys/ioctl.h>
#endif

#include "serialport.h"

int serialport_writebyte(SERIALPORT_HANDLE fd, uint8_t b){
#ifdef _WIN32
    DWORD errors;
    COMSTAT status;
    DWORD n;
    if(!WriteFile((HANDLE)fd, (void *)(&b), 1, &n, NULL)){
        ClearCommError((HANDLE)fd, &errors, &status);
        perror("serialport_write: Error writing to COM port");
        return -1;
    }
#else /* Linux, Mac */
    int n = write(fd,&b,1);
    if( n!=1)
        return -1;
#endif
    return 0;
}

int serialport_write(SERIALPORT_HANDLE fd, const char* str){
    int len = strlen(str);
#ifdef _WIN32
    DWORD n;
    DWORD errors;
    COMSTAT status;
    if(!WriteFile((HANDLE)fd, (void *)str, len, &n, 0)){
        ClearCommError((HANDLE)fd, &errors, &status);
        perror("serialport_write: Error writing to COM port");
        return -1;
    }
#else /* Linux, Mac */
    int n = write(fd, str, len);
    if( n!=len ) 
        return -1;
#endif
    return 0;
}

int serialport_read_until(SERIALPORT_HANDLE fd, char* buf, char until){
    char b[1] = " ";
    int i=0;
    do { 
#ifdef _WIN32
        DWORD n;
        if(!ReadFile(fd, b, 1, &n, NULL)){ /* READFILE returns TRUE on success */
            return -1;
        }
#else /* Linux, Mac */
        int n = read(fd, b, 1);  // read a char at a time
#endif
        if( n==-1){
#if _WIN32
            fprintf(stderr,"COULDN'T READ\n");
            return -1;    // couldn't read
        }else if( n==0 ) {
            //fprintf(stderr,"READ 0\n");
            usleep( 10 ); // wait 10 msec try again
            continue;
#else
            continue;
#endif
        }else{
            //fprintf(stderr,"%d=[%c]\n",i,b[0]);
            buf[i] = b[0];
            i = i + 1;
        }
        /* TODO add some code to check the length of the buffer is not exceeded */
    } while( b[0] != until );

    buf[i] = '\0';  // null terminate the string
    return 0;
}

int serialport_readbyte(SERIALPORT_HANDLE fd, char *b){
#ifdef _WIN32
    DWORD n;
    if(ReadFile(fd, b, 1, &n, NULL)){
        return -1;
    }
#else /* Linux, Mac */
    int n = read(fd, b, 1);  // read a char at a time
    if(n==1)return 0;
    return -1;
#endif
}


// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error


SERIALPORT_HANDLE serialport_init(const char *serialport, int baud, SERIALPORT_TERMIOS *oldsettings){
    SERIALPORT_HANDLE fd;
    
#ifdef _WIN32
   {fd = CreateFile(serialport, GENERIC_READ|GENERIC_WRITE, 0, NULL
            , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    );
    if(fd==INVALID_HANDLE_VALUE){
        DWORD error = GetLastError();
        switch(error){  
            case ERROR_ACCESS_DENIED:
                fprintf(stderr,"serislport_init: Unable to open port: Access denied (port in use?)\n");
                return SERIALPORT_FILE_ERROR;
            case ERROR_FILE_NOT_FOUND:
                fprintf(stderr,"serialport_init: Unable to open port '%s'. File not found.\n",serialport);
                return SERIALPORT_FILE_ERROR;
            default:
                perror("serialport_init: Unknown error in CreateFile call.");
                return SERIALPORT_FILE_ERROR;
        }
    }

    DCB toptions;
    if(!GetCommState(fd, &toptions)){
        perror("serialport_init: Couldn't get COM state");
        return SERIALPORT_FILE_ERROR;
    }

    // save old settings if requested
    if(oldsettings){
           *oldsettings = toptions;
    }

    DWORD brate;
    switch(baud) {
        case 4800:   brate=CBR_4800;   break;
        case 9600:   brate=CBR_9600;   break;
        case 14400:  brate=CBR_14400;  break;
        case 19200:  brate=CBR_19200;  break;
        case 38400:  brate=CBR_38400;  break;
        case 57600:  brate=CBR_57600;  break;
        case 115200: brate=CBR_115200; break;  
        // some other speeds are possible, see DCB structure details (this
        // 115200 is the max speed of arduino.
    }

    toptions.BaudRate = brate;
    toptions.ByteSize = 8;
    toptions.StopBits = ONESTOPBIT;
    toptions.Parity = NOPARITY; 
    toptions.fOutxCtsFlow = FALSE;
    toptions.fOutxDsrFlow = FALSE;
    toptions.fOutX = FALSE;
    toptions.fInX = FALSE;
    toptions.fRtsControl = RTS_CONTROL_DISABLE;
    toptions.fTXContinueOnXoff = TRUE;
    toptions.fNull = FALSE;
    
    
    if(!SetCommState(fd, &toptions)) {
        perror("serialport_init: Couldn't set COM state");
        return SERIALPORT_FILE_ERROR;
    }
    }

#else /* Linux, Mac */
   {struct termios toptions;
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);

    fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)  {
        perror("serialport_init: Unable to open port ");
        return SERIALPORT_FILE_ERROR;
    }
    
    if (tcgetattr(fd, &toptions) < 0) {
        perror("serialport_init: Couldn't get term attributes");
        return SERIALPORT_FILE_ERROR;
    }

    // save old settings if requested
    if(oldsettings){
           *oldsettings = toptions;
    }

    speed_t brate = baud; // let you override switch below if needed
    switch(baud) {
    case 4800:   brate=B4800;   break;
    case 9600:   brate=B9600;   break;
#ifdef B14400
    case 14400:  brate=B14400;  break;
#endif
    case 19200:  brate=B19200;  break;
#ifdef B28800
    case 28800:  brate=B28800;  break;
#endif
    case 38400:  brate=B38400;  break;
    case 57600:  brate=B57600;  break;
    case 115200: brate=B115200; break;
    }
    cfsetispeed(&toptions, brate);
    cfsetospeed(&toptions, brate);

    // 8N1
    toptions.c_cflag &= ~PARENB;
    toptions.c_cflag &= ~CSTOPB;
    toptions.c_cflag &= ~CSIZE;
    toptions.c_cflag |= CS8;
    // no flow control
    toptions.c_cflag &= ~CRTSCTS;

    toptions.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    toptions.c_oflag &= ~OPOST; // make raw

    // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
    toptions.c_cc[VMIN]  = 0;
    toptions.c_cc[VTIME] = 20;
    
    if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
        perror("serialport_init: Couldn't set term attributes");
        return SERIALPORT_FILE_ERROR;
    }
    }
#endif

    return fd;
}

int serialport_restoresettings(SERIALPORT_HANDLE fd, SERIALPORT_TERMIOS *oldsettings){

#ifdef _WIN32
    if(!SetCommState(fd, oldsettings)) {
        perror("serialport_restoresettings: Couldn't set COM state");
        return -1;
    }
#else
    if(tcsetattr(fd, TCSANOW, oldsettings) < 0) {
        perror("serialport_restoresettings: Couldn't set term attributes");
        return -1;
    }
#endif
    return 0;
}


int serialport_close(SERIALPORT_HANDLE fd){
    if(fd<0){
        perror("serialport_close: COM was not open");
        return -1;
    }
#ifdef _WIN32
    CloseHandle(fd);
#else /* Linux, Mac */
    close(fd);
#endif
    return 0;
}

serialport.h

#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <stdint.h>   /* Standard types */
#include <unistd.h>   /* UNIX standard function definitions */

#ifdef _WIN32
# include <windows.h>
# define SERIALPORT_TERMIOS DCB
# define SERIALPORT_HANDLE HANDLE
# define SERIALPORT_FILE_ERROR INVALID_HANDLE_VALUE
# define SERIALPORT_SLEEP(N) Sleep(N)
#else
# include <termios.h>
# define SERIALPORT_TERMIOS struct termios
# define SERIALPORT_HANDLE int
# define SERIALPORT_FILE_ERROR -1
# define SERIALPORT_SLEEP(N) usleep(N*1e3)
#endif

/**
    Open serial port for communication.
    @param serialport name of the port to be opened (eg /dev/tty.usbserial)
    @param baud baud rate (integer)
    @param oldsettings pointer to a struct in which old comm settings should be
    stored, if desired (else NULL if not desired)

    @return valid file descriptor, or -1 on error.
*/
SERIALPORT_HANDLE serialport_init(const char* serialport, int baud, SERIALPORT_TERMIOS *oldsettings);

int serialport_writebyte(SERIALPORT_HANDLE fd, uint8_t b);

/*
    Read a byte from the serial connection.
    @return 0 on success 
*/
int serialport_readbyte(SERIALPORT_HANDLE fd, char *byte);

int serialport_write(SERIALPORT_HANDLE fd, const char* str);

int serialport_read_until(SERIALPORT_HANDLE fd, char* buf, char until);

int serialport_restoresettings(SERIALPORT_HANDLE fd, SERIALPORT_TERMIOS *oldsettings);

/**
    Close a success-opened connection, return 0 on success.
*/
int serialport_close(SERIALPORT_HANDLE fd);

#endif

The port names come up the same every time, for a given board type.

Yes they will do but the names you use in your code must be the names that come up in the PC driver because that is what you code is trying to use.

Post the call to serialport_init.

It is being called with the right port string, and baud rate of 9600. The C code is wrapped using SWIG then accessed from Python. As I said, everything works absolutely fine with the Duemilanove, so I'm just looking here for issues that could arise uniquely due to the Mega. So far we have

  • port name is different
  • possibly slight differences in the FTDI USB-to-Serial interface?
  • I vaguely recall seeing some code that did something before creating the connection, but I haven't been able to locate that again.

It is being called with the right port string

I doubt it.

If you post the call, as I had asked, I'm willing to help.

My error message,

            case ERROR_FILE_NOT_FOUND:
                fprintf(stderr,"serialport_init: Unable to open port '%s'. File not found.\n",serialport);
                return SERIALPORT_FILE_ERROR;

indicates the correct port string was being used, so I'm confident that that's not the problem. Ultimately in the code the port string is defined in Python via 'port = "COM14"' or whatever it should be, as indicated in the Arduino IDE.

FWIW I found the Arduino code that does this stuff, there's not any magic going on as far as I can tell:

http://github.com/arduino/Arduino/blob/master/app/src/processing/app/Serial.java

COM ports are identified by a standard Java library, AFAICT:

http://download.oracle.com/docs/cd/E17802_01/products/products/javacomm/reference/api/javax/comm/CommPortIdentifier.html

It would be nice if there were a definite way of determining that a serial port exists but is currently in use. Otherwise I'm running out of ideas...

My error message, indicates the correct port string was being used, so I'm confident that that's not the problem.

Years ago I read a research article about confidence. The basic conclusion was: the more confident someone is, the more likely they are wrong. You seem a bit overconfident. :wink:

Ultimately in the code the port string is defined in Python via 'port = "COM14"' or whatever it should be, as indicated in the Arduino IDE

The Windows NT Kernel conveniently creates "DOS names" for serial ports 1 through 9. This allows the port to be opened like a normal file using "COM1" through "COM9". The Kernel does not create these names for serial ports greater than 9. "COM14" is treated as a normal file name. When your application tries to open "COM14", the operating system searches the working directory for a file by that name. The file obviously doesn't exist, so "file not found" is returned.

For serial ports above 9, the "device name" must be used. The device name isn't much different than the "DOS name" ... "\.\COMx" where "x" is the port number. In your case, the device name is "\.\COM14".

One caveat: C uses the backslash as an escape character. To specifiy the device name you will have to double-up on backslashes: "\\.\COM14".

Thanks for that. I will try it out when my student brings the Mega back again. FWIW I would say that I am ABSOLUTELY confident in the second law of thermodynamics. :slight_smile: