Go Down

Topic: Connect to Arduino Mega serial interface by C code (Read 9500 times) previous topic - next topic

John Pye

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

Grumpy_Mike

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?

John Pye

#2
Oct 05, 2010, 01:21 pm Last Edit: Oct 05, 2010, 01:21 pm by jdpipe Reason: 1
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?

Grumpy_Mike

Quote
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.  ::)

John Pye

#4
Oct 05, 2010, 02:53 pm Last Edit: Oct 05, 2010, 02:55 pm by jdpipe Reason: 1
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!

Grumpy_Mike

Quote
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.

John Pye

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.

Grumpy_Mike

Quote
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.

John Pye

#8
Oct 06, 2010, 12:27 am Last Edit: Oct 06, 2010, 12:27 am by jdpipe Reason: 1
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.

Coding Badly


John Pye

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.

Coding Badly


John Pye

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.

Code: [Select]
#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

Code: [Select]
#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

Grumpy_Mike

Quote
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.

Coding Badly


Go Up