RS232 и Android
У меня есть задача подключить к android-приложению моторизированный RFID-считыватель.
Есть панельный компьютер IEI AFL3-W07A-BT, который имеет два RS232.
Изучив вопрос с доступом к RS232 в андроид выбор пал на android-serial-api, т.к. кроме NDK похоже других альтернатив нет.
На этот ПК (панельку) был установлен Android x86 (8.1 Oreo).
В приложение добавлена библиотека на базе изученных примеров. (разработка под Linux не мой конек).
Но оказалось, что хоть запрос успешно и уходит к считывателю, но почему-то ответ всегда приходит поврежденным, и не совпадает с контрольной суммой в конце пакета.
На рабочем ПК был установлен Ubuntu 20.04 внутри WSL, код библиотеки переделан в простейшее консольное приложение. Когда приложение запускается внутри WSL, ответ приходит корректно, когда этот же самый код компилируется под Android или запускается на Ubuntu 20.04 на этой панельке ответ приходит не корректный всегда. Хоть пару бит, но в ответе меняется в случайных местах. Версии ядра у андроида и убунты разные, 4х и 5х соответственно.
Вот код тестового приложения:
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>
#define SOH 0x01
#define STX 0x02
#define ETX 0x03
#define EOT 0x04
#define ENQ 0x05
#define ACK 0x06
#define NAK 0x15
#define CAN 0x18
static speed_t getBaudrate(int baudrate)
{
switch (baudrate) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
default: return -1;
}
}
int configTTY(int fd, int baudrate)
{
struct termios cfg;
speed_t speed;
speed = getBaudrate(baudrate);
if (speed == -1) {
/* TODO: throw an exception */
printf("Invalid baudrate\n");
return -1;
}
printf("Configuring serial port\n");
memset(&cfg, 0, sizeof(cfg));
if (tcgetattr(fd, &cfg))
{
printf("tcgetattr() failed\n");
close(fd);
/* TODO: throw an exception */
return -2;
}
cfmakeraw(&cfg);
#ifdef ADDIDIONAL_CONFIG
cfg.c_cflag &= ~PARENB;
cfg.c_cflag &= ~CSTOPB;
cfg.c_cflag &= ~CSIZE;
cfg.c_cflag |= CS8;
cfg.c_cflag &= ~CRTSCTS;
cfg.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
cfg.c_oflag &= ~OPOST;
cfg.c_lflag = 0; // no signaling chars, no echo,
cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
cfg.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
cfg.c_iflag &= ~IGNBRK; // disable break processing
cfg.c_cc[VMIN] = 0; // read doesn't block
cfg.c_cc[VTIME] = 0; // 0.5 seconds read timeout
// enable reading
#endif
cfsetispeed(&cfg, speed);
cfsetospeed(&cfg, speed);
if (tcsetattr(fd, TCSANOW, &cfg))
{
printf("tcsetattr() failed\n");
close(fd);
/* TODO: throw an exception */
return -3;
}
printf("port settings done! ok!\n");
return 0;
}
void test(int fd)
{
char cmd_buffer[] = { 0x01, 0x43, 0x31, 0x31, 0x02, 0x03, 0x42 };
char response_buffer[256];
char dump_buffer[1025];
int bytes_available = 0;
char response;
char enq = ENQ;
ioctl(fd, FIONREAD, &bytes_available);
read(fd, response_buffer, bytes_available);
memset(response_buffer, 0, sizeof(response_buffer));
memset(dump_buffer, 0, sizeof(dump_buffer));
if (fd < 0)
printf("Bad file descriptor!\n");
write(fd, cmd_buffer, sizeof(cmd_buffer));
sleep(1);
read(fd, &response, 1);
if (response == ACK)
{
printf("OK! ACK was recieved!\n");
write(fd, &enq, 1);
sleep(1);
ioctl(fd, FIONREAD, &bytes_available);
int read_count = read(fd, response_buffer, bytes_available);
for (int i = 0; i < read_count; i++)
sprintf(dump_buffer + strlen(dump_buffer), "[%02X]", (0xff & response_buffer[i]));
printf("%s\n", dump_buffer);
}
else
printf("ACK was not recieved!\n");
}
int main( int argc, char** argv)
{
if (argc < 2)
{
printf("\n%s /dev/ttyS0 for select com1\n", argv[0]);
return 0;
}
int fd = open(argv[1], O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0 )
{
printf("Cannot open port!\n");
return -1;
}
if (configTTY(fd, 19200) < 0)
return -1;
test(fd);
close(fd);
return 0;
}
и Makefile
.PHONY: all clean install uninstall
LINK = g++
LFLAGS = -Wl,-E,-g,-Bstatic -lc -Wl,-Bdynamic
LIBS = $(SUBLIBS)
DEFINES =
CC = g++
CXX = g++
CXXFLAGS =-pipe -O0 -Wall -W -fPIC $(DEFINES) -Wl,-E -g -Wno-comment -Wno-unknown-pragmas -std=c++11 -static
LDFLAGS= -Bstatic
INCPATH = -I. -I. -Iinclude -I/usr/lib/include -I/usr/include/
SOURCES = main.cpp
OBJECTS = main.o
TARGET = wsl_serial_test
LIBS_DIR = /usr/lib/x86_64-linux-gnu
TEST = /usr/bin/test
first: all
####### Build rules
$(TARGET): $(OBJECTS)
$(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)
all: Makefile $(TARGET)
main.o: main.cpp
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o main.cpp
install:
sudo cp $(TARGET) /usr/bin/
sudo chown root:root /usr/bin/$(TARGET)
sudo chmod 755 /usr/lib/bin/$(TARGET)
clean:
find ./*.o && rm -f ./*.o
find ./wsl_serial_test && rm -f ./wsl_serial_test
Включение отключение блока кода внутри ADDIDIONAL_CONFIG похоже видимого эффекта не оказывает. Может есть здесь более опытные Linux-разработчики, кто может подсказать где может быть ошибка в коде. Считыватель по умолчанию работает на скорости 19200 / 8 бит данных / 1 стоповый / без контроля четности. Программа посылает один и тот же запрос ( получить версию ) и должна получить в ответ V2.02 (56-32-2E-30-32) + служебные поля и контрольная сумма. При получении команды считыватель отвечает байтом ACK, если контрольная сумма пакета и код команды совпадают. после получения байта ENQ считыватель возвращает ответ на команду. команда всегда начинается с символа STX и завершается символом ETX и после идет контрольная сумма (BCC) исключающее или всех байт пакета. Весьма странно, но ACK всегда доходит корректно, и команда всегда доходит до считывателя также корректно, повреждается только длинный ответ.
Тест на андроиде на панельном ПК

Тест из WSL на другом ПК результат всегда корректный, физический порт "проброшен" внутрь WSL из Windows.

Тест из Ubuntu на панельном ПК
