Мне была поручена реализация протокола ModBus через двухпроводную систему RS485. (На самом деле это три провода, A / B и GND).
ModBus — не главное, но шаг до этого … простой ввод-вывод через интерфейс.
Я использую конвертер FTDI USB-RS485 для подключения хоста Linux (не взаимозаменяемого) к хосту Windows (взаимозаменяемого с другим хостом Linux, хотя я бы хотел этого избежать)
Кодировка должна быть 19200, 8, n, 1.
Но это просто не похоже на работу.
У меня нет точного кода под рукой, но в Linux я делаю это:
int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
if(fd == -1) return "Error while opening the port";
Далее я настраиваю порт.
struct termios tty;
tcgetattr(fd, &tty);
cfsetispeed(&tty, B19200);
cfsetospeed(&tty, B19200);
tty.c_cflag = CS8; //Empties the cflags and sets the character width.
tty.c_cflag |= (CLOCAL | CREAD); //Sets 'recommended' options.
tty.c_lflag = 0;
tty.c_iflag = 0;
tty.c_oflag = 0;
tcgetattr(fd, TCSANOW, &tty);
Контроль четности и управления потоком в настоящее время не планируются, так как конечный результат будет подключен к низкоуровневой плате, где я должен сам позаботиться о сигналах. Кроме того, нет никаких проводов, которые позволили бы «беспрепятственное общение». (В конце концов, я не хочу, чтобы символ XON / XOFF ограничивал диапазон байтов, который я могу передавать)
Все эти функции проходят правильно и данные установлены.
В Windows я открываю последовательный порт следующим образом:
DCB SP;
HANDLE hSerial = CreateFile("COM6", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hSerial == INVALID_HANDLE_VALUE) return "Error while opening the port";
GetCommState(hSerial, &SP);
Четность отключена, а также управление потоком. Размер байта равен 8.
Редактировать:
Так как это было задано, вот мой код для скорости передачи данных в Windows (из памяти)
SP.DCBlength = sizeof (SP);
SP.BaudRate = 19200;
SP.Parity = NOPARITY;
SP.StopBits = ONESTOPBIT;
SetCommState (hSerial, &ИП);
Опять же, все эти функции работают без нареканий.
Теперь для теста, который вызывает у меня сильную головную боль.
На хосте Linux я создаю байтовый буфер размером 256 байт.
Этот буфер заполняется символьными значениями от 0 до 255 … и затем отправляется по проводам с записью.
В то же время, другая сторона ожидает «ReadFile» для получения данных.
При такой конфигурации как для «другого хоста Linux», так и для хоста Windows поступает 256 байт … однако это НЕ числа от 0 до 255, а что-то 00 06 и т. Д.
Я могу заставить Linuxhost работать, когда я устанавливаю все члены структуры termios в 0, прежде чем устанавливать параметры, которые мне действительно нужны. Я предполагаю, что это из-за управляющих символов … однако, если я сделаю это, хост Windows получит только 4 из 256 байтов.
Как я уже сказал, к сожалению, у меня нет удобного кода. Если у кого-то есть идеи, с какой точки я мог бы заняться этим, я был бы очень благодарен. Я опубликую больше кода, как только у меня будет доступ к нему снова.
Как я реализую операцию чтения:
DWORD nBytes = 0;
char Buffer[256], *ptr = Buffer;
int Rem = 256;
while(Rem) {
ReadFile(hSerial, ptr, Rem, &nBytes, 0);
Rem -= nBytes;
ptr += nBytes;
}
//Evaluate Buffer
Чтобы отметить, я установил таймауты, но не могу вспомнить точные значения.
Изменить: так как теперь у меня есть доступ к моему рабочему месту, вот фактический (текущий) код.
const char *InitCOM(const char *TTY) {
struct termios tty;
hSerial = open(TTY, O_RDWR | O_NOCTTY | O_NDELAY);
if(hSerial == -1) return "Opening of the port failed";
fcntl(hSerial, F_SETFL, 0);
if(tcgetattr(hSerial, &tty) != 0) return "Getting the parameters failed.";
if(cfsetispeed(&tty, B19200) != 0 || cfsetospeed(&tty, B19200) != 0) return "Setting the baud rate failed.";//CFlags
//Note: I am full aware, that there's an '=', and that it makes the '&=' obsolete, but they're in there for the sake of completeness.
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; //8-bit characters
tty.c_cflag |= (CLOCAL | CREAD);und erlaubt 'Lesen'.
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
//Input Flags
tty.c_iflag &= ~IGNBRK;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
//Local Flags
tty.c_lflag = 0;
//Output Flags
tty.c_oflag = 0;
//Control-Characters
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 5;
if(tcsetattr(hSerial, TCSAFLUSH, &tty) != 0) return "Setting the new parameters failed";
return NULL;
}
Что касается фактического кода отправки / получения:
int main(int argc, char* argv[]) {
#if defined FOR_PC
const char *err = InitCOM("/dev/ttyUSB0");
#else
const char *err = InitCOM("/dev/ttyS3");
#endif
if(err) printf("Error while initalizing: %s ErrNum: %d\n", err, errno);
else {
/*unsigned char C[256]; //Original code with the array
int nBytes;
#ifdef FOR_PC
int Rem = 256, ReqCount = 0;
unsigned char *ptr = C;
while(Rem > 0) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
nBytes = read(hSerial, ptr, Rem);
if(nBytes > 0) {
Rem -= nBytes;
ptr += nBytes;
++ReqCount;
}
}
printf("Number of received Bytes: %d in %d sends.\n\n", 256 - Rem, ReqCount);
for(int i = 0; i < 256; ++i) {
printf("%02X ", C[i]);
if((i%32) == 31) printf("\n");
}
#else
for(int i = 0; i < 256; ++i) C[i] = i;
nBytes = write(hSerial, C, 256);
printf("\nWritten Bytes: %d\n", nBytes);
#endif*/
//Single-Byte Code
unsigned char C = 0x55;
#ifdef FOR_PC
while(true) { //Keeps listening
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
read(hSerial, &C, 1);
printf("Received value 0x%02X\n", C);
}
#else
write(hSerial, &C, 1); //Sends one byte
#endif
close(hSerial);
}
return 0;
}
Что касается осциллографа: я проверил оба направления с отправкой. Оба отлично справились со своей работой.
Сигнал 0x55 — это постоянное увеличение / уменьшение на длине 50 микросекунд (как и должно быть, поэтому установка скорости передачи данных также не составляет проблем).
Так есть ли что-то в моем коде получения, что я делаю неправильно? «Выбрать» неправильно?
Ваша функция чтения может легко взорваться. Если вы находитесь рядом с концом буфера, и вы прочитали больше, чем сумма для его заполнения, вы скопируете за конец буфера, переписав стек.
На стороне отправки Linux вы должны смотреть в «сырой режим», например, cfmakeraw (). Таким образом, вас не будет беспокоить система, «помогающая» вам (например, добавление CR при отправке новой строки — действительно испортит двоичные данные …). В Microsoft есть способ сделать это, но я забыл как.
на стороне Windows, вы установили поле DCBLength в своей структуре DCB?
dcb.DCBlength = sizeof(dcb);