#c #linux #binary #serial-port #termios
#c #linux #двоичный #последовательный порт #termios
Вопрос:
В настоящее время я пытаюсь отправить необработанные двоичные данные в десятичном формате на внешнее устройство через serial. В настоящее время у меня есть данные в буферном массиве, но хотелось бы, чтобы они были в такой структуре:
struct packetData{
uint8_t sync1;
uint8_t sync2;
uint16_t messageId;
uint16_t dataWordCount;
uint16_t flags;
uint16_t checksum;
};
Я также использую 9600 бод, и у меня установлены все настройки termios cfmakeraw
, и в настоящее время я пишу с использованием:
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int flags = O_RDWR | O_NOCTTY | O_NDELAY;
fd = open(device, flags);
uint16_t buf_tx[BUFFER_SIZE] = {255,129,191,0,2057,0};
if(fd == -1){
printf("n Failed to open port! ");
return -1;
}
tcgetattr(fd, amp;tty); //Get the current attributes of the Serial port
cfmakeraw(amp;tty);
cfsetispeed(amp;tty, B9600); //Set read speed as 9600 baud
cfsetospeed(amp;tty, B9600); //Set write speed as 9600 baud
if((tcsetattr(fd, TCSANOW, amp;tty)) != 0){
printf("Error! Can't set attributes.n");
return -1;
}
else{
printf("Connection successful! n");
}
while(x < 1000){
memset(buf_tx, 0, sizeof(buf_tx));
tcflush(fd, TCOFLUSH);
if(y < 5){
if(write(fd, buf_tx, 5) == -1){
printf("n");
printf("Error>>: %sn",strerror(errno));
y ;
}
}
tcflush(fd, TCIOFLUSH);
usleep(1000);
x ;
}
Этот код не является полным кодом, а только частями настройки / записи, поэтому не нужно беспокоиться о его синтаксисе. если возможно, было бы неплохо не иметь этого буферного массива и просто использовать struct напрямую, но я возьму то, что смогу получить.
Комментарии:
1. вы можете просто использовать struct напрямую.
2. В чем ваш вопрос? Это не может быть просто «Как мне отправить двоичные данные через последовательный порт с использованием C»; это запрос «закодируйте это для меня», задача, для которой будет достаточно библиотеки.
3. @user253751 могу ли я просто поместить имя структуры прямо туда, куда я обычно помещаю буфер?
4.
write
принимает любой указатель. это не обязательно должен быть указатель на символ.5. Вам также нужен размер структуры, и, если вы хотите поддерживать свой протокол, вам может потребоваться указать свою структуру как упакованную.
Ответ №1:
Кажется, у вас более или менее открыт последовательный порт. Я предпочитаю сам явно устанавливать компоненты-члены termios, но cfmakeraw()
это тоже прекрасно.
Что вам следует учитывать, так это наличие отдельной функции для отправки одной или нескольких из этих структур одновременно. Например,
int write_all(const int fd, const void *buf, const size_t len)
{
const char *data = buf;
size_t written = 0;
ssize_t n;
while (written < len) {
n = write(fd, data written, len - written);
if (n > 0) {
written = n;
} else
if (n != -1) {
/* C library bug, should never occur */
errno = EIO;
return -1;
} else {
/* Error; n == -1, so errno is already set. */
return -1;
}
}
/* Success. */
return 0;
}
Функция вернет 0, если все данные были успешно записаны, и -1 с errno
набором, если возникает ошибка.
Для отправки struct packetData pkt;
просто используйте write_all(fd, amp;pkt, sizeof pkt)
.
Для отправки полного массива struct packetData pkts[5];
используйте write_all(fd, pkts, sizeof pkts)
. Для отправки n
пакетов, начинающихся с pkts[i]
, используйте write_all(fd, pkts i, n * sizeof pkts[0])
.
Однако вы не хотите использовать tcflush()
. Он не делает того, что вы думаете; на самом деле он просто отбрасывает данные.
Вместо этого, чтобы убедиться, что записанные вами данные были переданы, вам необходимо использовать tcdrain(fd)
.
Я не рекомендую добавлять tcdrain(fd)
в конце write_all()
функции, потому что она блокирует, приостанавливает работу программы до тех пор, пока данные не будут переданы. Это означает, что вы должны использовать только tcdrain()
перед тем, как сделать что-то, что требует, чтобы другой конец получил передачу; например, перед попыткой прочитать ответ.
Однако, если это интерфейс запроса-ответа, и вы намерены также считывать данные с последовательного устройства, вы должны установить tty.c_cc[VMIN]
и tty.c_cc[VTIME]
отразить, как вы собираетесь использовать интерфейс. Я предпочитаю асинхронную полнодуплексную работу, но для этого требуется select()
/ poll()
обработка. Для полудуплексного режима, только с этими точными структурами, вы можете использовать tty.c_cc[VMIN] = sizeof (struct packetData)
with say tty.c_cc[VTIME] = 30
, что заставляет read()
пытаться ждать, пока не будет доступна полная структура, но не более 30 решающих секунд (3,0 секунды). Что-то подобное tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 1;
встречается чаще; это приводит read()
к возврату короткого счета (даже 0!), Если в течение решающей секунды (0,1 секунды) не получено никаких дополнительных данных. Тогда функция приема может быть следующей:
int read_all(const int fd, void *buf, const size_t len)
{
char *const ptr = buf;
size_t have = 0;
ssize_t n;
/* This function is to be used with half-duplex query-response protocol,
so make sure we have transmitted everything before trying to
receive a response. Also assumes c_cc[VTIME] is properly set for
both the first byte of the response, and interbyte response interval
in deciseconds. */
tcdrain(fd);
while (have < len) {
n = read(fd, ptr have, len - have);
if (n > 0) {
have = n;
} else
if (n == 0) {
/* Timeout or disconnect */
errno = ETIMEDOUT;
return -1;
} else
if (n != -1) {
/* C library bug, should never occur */
errno = EIO;
return -1;
} else {
/* Read error; errno set by read(). */
return -1;
}
}
/* Success; no errors. */
return 0;
}
Если это возвращает -1
with errno == ETIMEDOUT
, другой стороне потребовалось слишком много времени, чтобы ответить. В буфере может быть остаток от позднего ответа, который вы можете отбросить с tcflush(TCIFLUSH)
помощью (или с tcflush(TCIOFLUSH)
помощью , которая также отбрасывает любые записанные данные, которые еще не переданы). Синхронизация в этом случае немного затруднена, потому что вышеупомянутая read_all()
функция не возвращает, сколько байтов она получила (и, следовательно, сколько байтов нужно отбросить из частичной структуры).
Иногда используемый интерфейс всегда возвращает количество байтов, но также устанавливает errno
( 0
значение, если ошибка не произошла, и ненулевую константу ошибки в противном случае). Это было бы лучше для функций чтения и записи интерфейса «запрос-ответ», но многие программисты считают этот вариант использования «странным», хотя он вполне соответствует стандарту POSIX.1 (который является соответствующим стандартом здесь).
Комментарии:
1. Ваше описание tcflush() против tcdrain() является точным, но есть множество других неточностей, особенно ваши примеры VMIN и VTIME.
2. @sawdust: Вы имеете в виду, например, тот факт, что
.c_cc[VTIME]
определяет не тайм-аут чтения () (как можно было бы предположить из первого упоминания), а фактически межбайтовый тайм-аут, т.Е. Наличие.c_cc[VMIN]=5
и.c_cc[VTIME]=5
, возможно, время ожидания операции чтения после чуть менее 30 решающих секунд? Я оставил эту информацию на странице руководства. Нет, я считаю, что вы только выдвигаете необоснованные претензии (в частности, «многочисленные»). Не стесняйтесь писать лучший ответ вместо язвительного комментария, пожалуйста.3. Вместо ответа на простой вопрос вы предоставляете дополнительную, но вводящую в заблуждение информацию. Вы не замечаете, что последовательный терминал открыт для неблокирующего режима, поэтому настройки VMIN и VTIME будут проигнорированы. «вы можете использовать … tty.c_cc[VTIME] = 30» — даже если использовался режим блокировки, тайм-аут в 3 секунды плохо объясняется (пока вы с опозданием не добавите комментарий о том, что следует обратиться к справочной странице) и кажется преувеличенным. «tty.c_cc[VMIN] = 1 … возвращайте короткое число (даже 0!), Если …» — Нет, когда VMIN> 0 (и режим блокировки) всегда возвращается хотя бы один байт (пока устройство подключено).
4. «В буфере может быть остаток от позднего ответа, который вы можете отбросить с помощью tcflush (TCIFLUSH) …» — Плохой совет IMO, поскольку программа пользовательского пространства не синхронизирована с входящими данными. Вместо этого используйте надежный код, который может обрабатывать все входные данные и проверять их. Не предполагайте, что сообщения совпадают с системными вызовами чтения. Не предполагайте, что полное сообщение получено за один системный вызов read.