4

I'm working on a new project where I want to make a connection with an FTDI which is connected to my debian machine. I am intending to write the code with C, not C++. Here lies my problem. All the examples I find are incomplete or are made for a c++ compiler in stead of the GCC compiler.

The goal is to talk to my microcontroller which is connected to the FTDI. For debugging i want to start building a linux application which is able to:

  • initialize a serial connection on startup with ttyUSB1
  • send a character string
  • display character strings when they are received by the pc
  • save the communication to a .txt file

Is there any example code or tutorial to make this?

If I succeed I will defenetly place the code here so that new viewers can use it to!

Edit:

Like I said I would post the code if I had it, and this is what worked for me:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#define MODEM "/dev/ttyUSB0"
#define BAUDRATE B115200    

int main(int argc,char** argv)
{   
    struct termios tio;
    struct termios stdio;
    struct termios old_stdio;
    int tty_fd, flags;
    unsigned char c='D';
    tcgetattr(STDOUT_FILENO,&old_stdio);
    printf("Please start with %s /dev/ttyS1 (for example)\n",argv[0]);
    memset(&stdio,0,sizeof(stdio));
    stdio.c_iflag=0;
    stdio.c_oflag=0;
    stdio.c_cflag=0;
    stdio.c_lflag=0;
    stdio.c_cc[VMIN]=1;
    stdio.c_cc[VTIME]=0;
    tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
    tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);       // make the reads non-blocking
    memset(&tio,0,sizeof(tio));
    tio.c_iflag=0;
    tio.c_oflag=0;
    tio.c_cflag=CS8|CREAD|CLOCAL;           // 8n1, see termios.h for more information
    tio.c_lflag=0;
    tio.c_cc[VMIN]=1;
    tio.c_cc[VTIME]=5;
    if((tty_fd = open(MODEM , O_RDWR | O_NONBLOCK)) == -1){
        printf("Error while opening\n"); // Just if you want user interface error control
        return -1;
    }
    cfsetospeed(&tio,BAUDRATE);    
    cfsetispeed(&tio,BAUDRATE);            // baudrate is declarated above
    tcsetattr(tty_fd,TCSANOW,&tio);
    while (c!='q'){
        if (read(tty_fd,&c,1)>0){
            write(STDOUT_FILENO,&c,1); // if new data is available on the serial port, print it out
            printf("\n");
        }
        if (read(STDIN_FILENO,&c,1)>0){
            write(tty_fd,&c,1);//if new data is available on the console, send it to serial port
            printf("\n");
        }
    }
    close(tty_fd);
    tcsetattr(STDOUT_FILENO,TCSANOW,&old_stdio);
    return EXIT_SUCCESS;
}

Most of the code came from http://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux but i also used a bit from the code posted below.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Embed
  • 116
  • 1
  • 1
  • 9
  • 1
    There's even a [nice howto dedicated to that subject](http://tldp.org/HOWTO/Serial-Programming-HOWTO/). – fvu Oct 17 '13 at 13:07
  • 2
    did you check this question http://stackoverflow.com/questions/2982552/correct-initialization-sequence-for-linux-serial-port?rq=1 – 999k Oct 17 '13 at 13:08
  • What's wrong with minicom? – KBart Oct 17 '13 at 13:09
  • Good question, later i want to rebuild the program to log and execute commands without having me behind the pc so this concept is just the beginning. – Embed Oct 17 '13 at 13:11
  • This post http://stackoverflow.com/questions/2982552/correct-initialization-sequence-for-linux-serial-port?rq=1 only shows some initialisation. – Embed Oct 17 '13 at 13:13
  • The first comment in this list, i cant rate it up but its an awsome e-how. I'l start working it through. I'l post if i have something to show. – Embed Oct 17 '13 at 13:17
  • 1
    I see the point. Well, you can at least look at minicom source code (http://alioth.debian.org/frs/?group_id=30018&release_id=1863#source-_2.6.2-title-content). It's open source and written in C, also heavily used for many years, so I'm sure there is a lot to reuse/learn from. – KBart Oct 17 '13 at 13:19
  • @KBart it's indeed a *reliable* source, but it's not that easy to read/reuse as it's does lots&lots more stuff than OP needs/wants. – fvu Oct 17 '13 at 13:22
  • @fvu I'm not saying that it's simple, but it usually takes less time to get into an example and take the parts you need than try to reinvent your own wheel. Not to mention fewer bugs and design fails.. – KBart Oct 17 '13 at 13:32
  • Ok, to come back at the http://tldp.org/HOWTO/Serial-Programming-HOWTO/x165.html . It's a nice explenation but the example code does not work. Not only the blank main() looks suspicious, the code return with the following errors at compiling: – Embed Oct 17 '13 at 14:38
  • Errors: NonCanonicalSerial.C: In function 'int main()': NonCanonicalSerial.C:20:42: error: 'exit' was not declared in this scope NonCanonicalSerial.C:22:31: error: 'bzero' was not declared in this scope NonCanonicalSerial.C:33:24: error: 'read' was not declared in this scope – Embed Oct 17 '13 at 14:38
  • https://github.com/ynezz/librs232/ – Maquefel Oct 18 '13 at 11:12

2 Answers2

3

Handling with serial ports ( for linux OS ) :
- To open communication, you will need a descriptor which will be the handle for your serial port.
- Set the flags to control how the comunication will be.
- Write the command to this Handle ( make sure you're formatting the input correctly ).
- Get the answer. (make sure you're to read the amount of information you want )
- Close the handle. It will seem like this:

int fd; // file descriptor
int flags; // communication flags
int rsl_len; // result size
char message[128]; // message to send, you can even dinamically alocate.
char result[128]; // result to read, same from above, thanks to @Lu

flags = O_RDWR | O_NOCTTY; // Read and write, and make the job control for portability
if ((fd = open("/dev/ttyUSB1", flags)) == -1 ) {
  printf("Error while opening\n"); // Just if you want user interface error control
  return -1;
}
// In this point your communication is already estabilished, lets send out something
strcpy(message, "Hello");
if (rsl_len = write(fd, message, strlen(message)) < 0 ) {
  printf("Error while sending message\n"); // Again just in case
  return -2;
}
if (rsl_len = read(fd, &result, sizeof(result)) < 0 ) {
  printf("Error while reading return\n");
  return -3;
}
close(fd);

Note that you have to care about what to write and what to read. Some more flags can be used in case of parity control, stop bits, baud rate and more.

rfermi
  • 179
  • 11
  • Of course this code does not handle `E_INTR`, doesn't open the file handles using `O_CLOEXEC`, doesn't handle signals... This is not how you want to tell a newbie to write good Unix code. Once you get this to a point where it's good Unix code, it will be so long for the little it does, there's a question of whether it's even worth doing any longer in plain C without losing your sanity... – Kuba hasn't forgotten Monica Oct 17 '13 at 21:46
  • @KubaOber At the other end of the spectrum one can say that Qt is so large and all-encompassing in what it does that there's a question of whether it's even worth using. I have seen Qt brought in as a dependency on projects which only used it for a very small portion of the functionality (for example: basic data structures, which are far more sensibly implemented using STL) and basically the price you pay is bloat. I figure it's easier to hold on to your sanity doing it this way... It's building your car from scratch rather than being at the mercy of the manufacturer for replacement parts – Steven Lu May 14 '14 at 02:18
  • I may have chosen a terrible analogy as without extraordinary resources, the car you end up with will be not fit for use. POSIX, though, is a bit less challenging, mostly because it's well documented. – Steven Lu May 14 '14 at 02:19
  • @StevenLu Qt is quite usable even if you don't bring in the entire modules, but just selected parts of them. This is, generally, done automatically if you statically link the Qt library to your executable. As an extra bonus, you can certainly include only certain Qt *source* files in your project, just like Qt does when it bootstraps itself. On Windows, a completely statically compiled hello world that uses QString, QTextStream on a `stdout` from cstdio, without threading support, with no dependencies on any runtimes, is around 800kb without trying super hard. – Kuba hasn't forgotten Monica May 14 '14 at 09:53
  • Yeah, but 800kB is still enormous for how little actual functionality that binary provides! – Steven Lu May 14 '14 at 13:17
  • 1
    @rfermi I'm pretty sure the `message` and `result` need to be sufficiently large `char[]`s. – Steven Lu May 14 '14 at 13:22
  • @StevenLu It provides quite a bit, in fact, although most of it is not used. I remind you that there are no dependencies on any other runtimes. Now try and bundle a C# hello world with a .net runtime redistributable :) – Kuba hasn't forgotten Monica May 15 '14 at 11:24
0

Since gcc is a C/C++ compiler, you don't need to limit yourself to pure C.

Sticking to pure C is OK if you enjoy writing lots of boilerplate code, and if you really know what you're doing. Many people use Unix APIs incorrectly, and a lot of example code out there is much too simplistic. Writing correct Unix C code is somewhat annoying, to say the least.

Personally, I'd suggest using not only C++, but also a higher-level application development framework like Qt. Qt 5 comes bundled with a QtSerialPort module that makes it easy to enumerate the serial ports, configure them, and get data into and out of them. Qt does not force you to use the gui modules, it can be a command line application, or a non-interactive server/daemon.

QtSerialPort is also usable from Qt 4, but it doesn't come bundled with Qt 4, you have to add it to your project. I suggest starting out with Qt 5, it's nicer to use with its C++11 support.

The code using Qt can be pretty simple, not much longer than your plain-English description. The below is a Qt console application using Qt 5 and C++11. It uses the core and serialport modules. It also handles the SIGINT signal so that the output file gets flushed before the process would terminate due to a ^C. I'm using QLocalSocket in place of raw Unix API to communicate from the Unix signal handler, the functionality is the same.

Only the code within main is strictly required, the rest is just to make it properly wrap things up when you hit ^C.

#include <QCoreApplication>
#include <QSerialPort>
#include <QFile>
#include <QTextStream>
#include <QLocalServer>
#include <QLocalSocket>
#include <cstdio>
#include <csignal>

QLocalSocket * xmit;

static void signalHandler(int)
{
    xmit->write(" ");
    xmit->flush();
}

static bool setupSignalHandler()
{
    QLocalServer srv;
    srv.listen("foobarbaz");
    xmit = new QLocalSocket(qApp);
    xmit->connectToServer(srv.serverName(), QIODevice::WriteOnly);
    srv.waitForNewConnection();
    QLocalSocket * receive = srv.nextPendingConnection();
    receive->setParent(qApp);
    qApp->connect(receive, &QLocalSocket::readyRead, &QCoreApplication::quit);
    struct sigaction sig;
    sig.sa_handler = signalHandler;
    sigemptyset(&sig.sa_mask);
    sig.sa_flags = SA_RESTART;
    return ! sigaction(SIGINT, &sig, NULL);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    setupSignalHandler();

    QSerialPort port("ttyUSB1");
    QFile file("file.txt");
    QTextStream err(stderr, QIODevice::WriteOnly);
    QTextStream out(stdout, QIODevice::WriteOnly);
    if (!file.open(QIODevice::WriteOnly)) {
        err << "Couldn't open the output file" << endl;
        return 1;
    }
    if (!port.open(QIODevice::ReadWrite)) {
        err << "Couldn't open the port" << endl;
        return 2;
    }
    port.setBaudRate(9600);
    QObject::connect(&port, &QSerialPort::readyRead, [&](){
        QByteArray data = port.readAll();
        out << data;
        file.write(data);
    });
    out << "Use ^C to quit" << endl;
    return a.exec();
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Sounds promising but how do i add the Qt librarys to GCC? I just installed QT5 but still comes up with "gcc -o qtSerial qtSerial.cpp qtSerial.cpp:1:28: fatal error: QCoreApplication: No such file or directory" when compiling. – Embed Oct 18 '13 at 09:17
  • Well, you can't do it manually without understanding what goes on. Use QT creator to create a project, add your files to the project, add the `serialport` module to the .pro file (`QT += serialport`), and it should work. – Kuba hasn't forgotten Monica Oct 18 '13 at 12:36
  • required language was C and OS was unix. Downvoted as this two requirement wasn't respected. – Lesto Oct 10 '14 at 20:34
  • @lesto I offered an alternative solution, and this most certainly works on any Unix system where you can compile Qt (anything worthwhile, essentially) :) – Kuba hasn't forgotten Monica Oct 10 '14 at 23:22