Host-клиент Arduino на C (Linux)

Введение

Задача написания host-клиента для Arduino на языке программирования C/C++ на первый взгляд представляется простой, поскольку работа с виртуальным COM-портом в Linux мало чем отличается от чтения/записи данных в файл (в данном случае в файл устройства /dev/ttyUSBx или /dev/ttyACMx), однако при попытке его написания лично я сразу же столкнулся с тем, что написанная мною программа исправно работала только после того, как я предварительно запускал и закрывал Arduino IDE.

Вполне понятно, что перед тем, как считывать и отправлять данные в виртуальный COM-порт Arduino, мне предварительно следовало бы инициализировать его так же, как это делает Java-класс RXTX, используемый Arduino IDE для коммуникации с Arduino.

К счастью, вопрос оказался не столь сложным; простое чтение документации man termios сразу же дало мне подсказку использовать утилиту stty для сравнения параметров последовательного порта до запуска Arduino IDE и после, чтобы определить список параметров, которые необходимо инициализировать:

stty -a -F /dev/ttyUSB0

Как выяснилось, инициализировать надо решительно все параметры без исключения.

После успешной инициализации последовательного порта обмен данными с Arduino в Linux ничем не отличается от обмена данными с любым другим терминальным устройством: функции fread() или read() для чтения данных, отправляемых Arduino USB-хосту, и функции записи данных fwrite() или write() -- в зависимости от типа используемой переменной файлового дескриптора (FILE* или int).

Host-клиент Arduino на C

В качестве прошивки для примера программы, осуществляющей коммуникацию с Arduino, я использовал скетч DumpMon из числа примеров к библиотеке Simple Dumping Monitor. Нижеприведённая программа host-клиента:

  1. открывает указываемый пользователем в аргументах коммандной строки файл устройства,

  2. инициализирует виртуальный последовательный порт (при этом происходит автоматический сброс Arduino, если плата оснащена функцией авто-сброса),

  3. считывает вывод загрузчика Arduino (это требуется, чтобы пропустить момент ожидания загрузчиком Arduino команд от хоста, иначе отправляемую с хоста команду получит загрузчик, а не код скетча),

  4. отправляет в скетч команду ? (вывод справки библиотеки Simple Dumping Monitor),

  5. считывает вывод скетча,

  6. завершает свою работу.

Вот исходный текст примера host-клиента Arduino на C (скачать):

/**

* @file arduino_serial.c Example of communication with Arduino in Linux.

* @author Andrey Sharoyko

* @created 2012/09/18

* @seealso http:///sites.google.com/site/vanyambauseslinux/

*/

#include <stdlib.h>

#include <stdio.h>

#include <errno.h>

#include <termios.h>

#include <unistd.h>

#include <string.h>


/**

* Get Arduino device file name from command line arguments.

*/

char* getDeviceFileName(int argc, char **argv)

{

if (argc < 2) {

fprintf(stderr, "Argument missing: Arduino device file.\n");

exit(EXIT_FAILURE);

}

return argv[1];

}


/**

* Open Arduino device file.

*/

int openDeviceFile(const char* fileName, const char* mode)

{

FILE* file;

file = fopen(fileName, mode);

if (file == NULL) {

perror(fileName);

exit(EXIT_FAILURE);

}

return fileno(file);

}


/**

* Get baud rate from command line arguments.

*/

unsigned int getBaudRate(int argc, char **argv)

{

return (argc > 2) ? atoi(argv[2]) : 57600;

}


/**

* Set Arduino serial port attributes.

*/

void setAttr(int fd, unsigned int baud)

{

struct termios toptions;

speed_t brate;

if (tcgetattr(fd, &toptions) < 0) {

perror("Can't get term attributes");

exit(EXIT_FAILURE);

}

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;

case 230400: brate=B230400; break;

default:

fprintf(stderr, "Invalid baud rate value: %d\n", baud);

exit(EXIT_FAILURE);

}

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("Can't set term attributes");

exit(EXIT_FAILURE);

}

}


/**

* Send command to Arduino.

*/

void sendCommand(int fd, const char *cmd)

{

write(fd, cmd, strlen(cmd));

}


/**

* Display command result from Arduino.

*/

void displayResult(int fd)

{

char buf[256];

ssize_t sz;

do {

sz = read(fd, buf, 255);

buf[sz] = 0;

printf("%s", buf);

}

while (sz);

}


/**

* Main function.

*/

int main(int argc, char *argv[])

{

char *fileName;

const char fileMode[] = "r+";

unsigned int baud;

int fd;

fileName = getDeviceFileName(argc, argv);

fd = openDeviceFile(fileName, fileMode);

baud = getBaudRate(argc, argv);

setAttr(fd, baud);

displayResult(fd);

sendCommand(fd, "?");

displayResult(fd);

printf("\n");

close(fd);

return EXIT_SUCCESS;

}

Компиляция с помощью GNU/C выполняется командой:

gcc -o arduino_serial arduino_serial.c

После успешной компиляции запуск на выполнение производится командой:

./arduino_serial /dev/ttyUSB0 19200

Автор: Андрей Шаройко <vanyamboe@gmail.com>