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-клиента:
открывает указываемый пользователем в аргументах коммандной строки файл устройства,
инициализирует виртуальный последовательный порт (при этом происходит автоматический сброс Arduino, если плата оснащена функцией авто-сброса),
считывает вывод загрузчика Arduino (это требуется, чтобы пропустить момент ожидания загрузчиком Arduino команд от хоста, иначе отправляемую с хоста команду получит загрузчик, а не код скетча),
отправляет в скетч команду ? (вывод справки библиотеки Simple Dumping Monitor),
считывает вывод скетча,
завершает свою работу.
Вот исходный текст примера 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>