Skip to content

Instantly share code, notes, and snippets.

@h4tr3d
Created July 6, 2024 04:34
Show Gist options
  • Save h4tr3d/143cc4b3767e3ebc6221ef70c982e63e to your computer and use it in GitHub Desktop.
Save h4tr3d/143cc4b3767e3ebc6221ef70c982e63e to your computer and use it in GitHub Desktop.
SerialSend (2024)
// [SerialSend.c]
// Эта программа посылает текст или файл через последовательный порт
// Автор Ted Burke, последнее обновление 8-4-2015
//
// Обновление:
// 2024-06-07: Alexander Drozdoff <adrozdoff@gmail.com>:
// - Добавлено отправка данных из файла as-is
// - Добавлен режим /read_back для вычитывания после
// отправки данных из порта. Полезно для синхронных
// устройство которые отсылают эхо, но которые блокируются
// если выходной буфер заполнен. Все вычитанные данные
// отбрасываются.
//
// Текст для отправки указывается в аргументах командной строки.
// По умолчанию используется COM-порт с самым большим найденным номером.
// Используемая по умолчанию скорость 38400 baud.
//
// Как компилировать с помощью установленной системы MinGW:
//
// gcc -o SerialSend.exe SerialSend.c
//
// Как компилировать компилятором cl компании Microsoft:
//
// cl SerialSend.c
//
// Пример запуска (отправка последовательности символов S365 E120):
//
// SerialSend.exe "S356 E120"
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
static DWORD write_serial_port(HANDLE hSerial, const void *ptr, size_t size, const char* dev_name, int read_back);
int main(int argc, char *argv[])
{
// Декларация переменных и структур:
int m, n;
unsigned char buffer[MAX_PATH];
unsigned char text_to_send[MAX_PATH];
unsigned char digits[MAX_PATH];
unsigned char *file_data = NULL;
unsigned int file_size = 0;
int baudrate = 38400;
int dev_num = 50;
int parse_hex_bytes = 0;
int close_delay = 0;
int read_back = 0;
char dev_name[MAX_PATH] = {0};
char file_name[MAX_PATH] = {0};
HANDLE hSerial;
DCB dcbSerialParams = {0};
COMMTIMEOUTS timeouts = {0};
// Вывод приветственного сообщения:
fprintf(stderr, "SerialSend (last updated 06-07-2024)\n");
fprintf(stderr, "See http://batchloaf.com for more information\n");
// Парсинг аргументов командной строки:
int argn = 1;
strcpy(buffer, "");
while(argn < argc)
{
if (strcmp(argv[argn], "/baudrate") == 0)
{
// Parse baud rate
if (++argn < argc && ((baudrate = atoi(argv[argn])) > 0))
{
fprintf(stderr, "%d baud specified\n", baudrate);
}
else
{
fprintf(stderr, "Baud rate error\n");
return 1;
}
}
else if (strcmp(argv[argn], "/devnum") == 0)
{
// Обработка номера устройства. SerialSend начинает поиск доступных
// COM-портов с этого номера, постепенно уменьшая его до нуля.
if (++argn < argc)
{
dev_num = atoi(argv[argn]);
fprintf(stderr, "Device number %d specified\n", dev_num);
}
else
{
fprintf(stderr, "Device number error\n");
return 1;
}
}
else if (strcmp(argv[argn], "/closedelay") == 0)
{
// Обработка задержки перед закрытием порта. После передачи
// указанного текста SerialSend выполнит задержку на это
// количество миллисекунд, после чего закроет COM-порт.
if (++argn < argc)
{
close_delay = atoi(argv[argn]);
fprintf(stderr, "Delay of %d ms specified before closing COM port\n", close_delay);
}
else
{
fprintf(stderr, "Close delay error\n");
return 1;
}
}
else if (strcmp(argv[argn], "/hex") == 0)
{
// Обработка hex-флага для передаваемого байта.
// Если этот флаг установлен, то в строку передачи можно
// добавить произвольный байт, указывая его через нотацию '\x'.
// Например, команда "SerialSend /hex Hello\x0D"
// отправит 6 байт, где последним будет байт перевода строки CR
// (символ '\r', у которого код 0x0D).
parse_hex_bytes = 1;
}
else if (strcmp(argv[argn], "/file") == 0)
{
// Будем читать данные из файла, сохраняем его имя,
// любой другой текст отправим после отправки файла
if (++argn < argc)
{
strncpy(file_name, argv[argn], sizeof(file_name));
fprintf(stderr, "File name %s specified\n", file_name);
}
else
{
fprintf(stderr, "File name error\n");
return 1;
}
}
else if (strcmp(argv[argn], "/read_back") == 0)
{
// Инициируем вычитывания данных из порта после записи.
// Иногда нужно для некоторых устройств, которые синхронно шлют эхо
// и нужно его вычитать, иначе другие команды не принимаются
read_back = 1;
}
else
{
// Этот аргумент командной строки будет отправляемым текстом:
strcpy(buffer, argv[argn]);
}
// Переход к следующему аргументу командной строки:
argn++;
}
// Проверка, предоставлен ли для передачи какой-нибудь текст:
if (strlen(buffer) == 0 && strlen(file_name) == 0)
{
fprintf(stderr, "Usage:\n\n\tSerialSend [/baudrate BAUDRATE] ");
fprintf(stderr, "[/devnum DEVICE_NUMBER] [/file FILE_NAME] [/read_back] [/hex] \"TEXT_TO_SEND\"\n");
return 1;
}
// Если hex-парсинг разрешен, то текст для передачи модифицируется:
n = 0; m = 0;
while(n < strlen(buffer))
{
if (parse_hex_bytes && buffer[n] == '\\')
{
n++;
if (buffer[n] == '\\') text_to_send[m] = '\\';
else if (buffer[n] == 'n') text_to_send[m] = '\n';
else if (buffer[n] == 'r') text_to_send[m] = '\r';
else if (buffer[n] == 'x')
{
digits[0] = buffer[++n];
digits[1] = buffer[++n];
digits[2] = '\0';
text_to_send[m] = strtol(digits, NULL, 16);
}
}
else
{
text_to_send[m] = buffer[n];
}
m++; n++;
}
text_to_send[m] = '\0'; // Null-символ, завершающий строку.
// Прочитаем файл в память
if (strlen(file_name))
{
FILE *fp = fopen(file_name, "rb+");
size_t readed = 0;
if (!fp)
{
fprintf(stderr, "Can't open file %s\n", file_name);
return 1;
}
// Классический "наивный" способ узнать размер файла
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
file_data = (unsigned char*)malloc(file_size);
if (!file_data)
{
fprintf(stderr, "Can't allocate memory for file reading\n");
return 1;
}
if (fread(file_data, 1, file_size, fp) != file_size)
{
fprintf(stderr, "Error during file reading: %d, %s\n", errno, strerror(errno));
return 1;
}
fclose(fp);
}
// Открыть доступный порт с самым большим номером:
fprintf(stderr, "Searching serial ports...\n");
while(dev_num >= 0)
{
fprintf(stderr, "\r ");
fprintf(stderr, "\rTrying COM%d...", dev_num);
sprintf(dev_name, "\\\\.\\COM%d", dev_num);
hSerial = CreateFile(
dev_name, GENERIC_READ|GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if (hSerial == INVALID_HANDLE_VALUE) dev_num--;
else break;
}
if (dev_num < 0)
{
fprintf(stderr, "No serial port available\n");
return 1;
}
fprintf(stderr, "OK\n");
// Установка параметров устройства (38400 baud, 1 start bit,
// 1 stop bit, no parity)
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if (GetCommState(hSerial, &dcbSerialParams) == 0)
{
fprintf(stderr, "Error getting device state\n");
CloseHandle(hSerial);
return 1;
}
//dcbSerialParams.BaudRate = CBR_38400;
dcbSerialParams.BaudRate = baudrate;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if(SetCommState(hSerial, &dcbSerialParams) == 0)
{
fprintf(stderr, "Error setting device parameters\n");
CloseHandle(hSerial);
return 1;
}
// Настройки таймаута COM-порта:
timeouts.ReadIntervalTimeout = 1;
timeouts.ReadTotalTimeoutConstant = 1;
timeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if(SetCommTimeouts(hSerial, &timeouts) == 0)
{
fprintf(stderr, "Error setting timeouts\n");
CloseHandle(hSerial);
return 1;
}
// Отправка файла
if (file_data)
{
DWORD bytes_written = 0;
fprintf(stderr, "Sending file... ");
bytes_written = write_serial_port(hSerial, file_data, file_size, dev_name, read_back);
fprintf(stderr, "\n%d bytes written to %s\n", bytes_written, dev_name);
}
// Отправка указанного текста:
if (m > 0)
{
DWORD bytes_written = 0;
fprintf(stderr, "Sending text... ");
bytes_written = write_serial_port(hSerial, text_to_send, m, dev_name, read_back);
fprintf(stderr, "\n%d bytes written to %s\n", bytes_written, dev_name);
}
// Сброс (Flush) буфера передачи перед закрытием последовательного порта:
FlushFileBuffers(hSerial);
if (close_delay > 0)
{
fprintf(stderr, "Delaying for %d ms before closing COM port... ", close_delay);
Sleep(close_delay);
fprintf(stderr, "OK\n");
}
// Закрытие последовательного порта:
fprintf(stderr, "Closing serial port...");
if (CloseHandle(hSerial) == 0)
{
fprintf(stderr, "Error\n", dev_name);
return 1;
}
fprintf(stderr, "OK\n");
// Нормальный выход:
return 0;
}
static DWORD write_serial_port(HANDLE hSerial, const void *ptr, size_t size, const char* dev_name, int read_back)
{
unsigned char *text_to_send = (unsigned char*)ptr;
DWORD bytes_written, total_bytes_written = 0;
while(total_bytes_written < size)
{
if(!WriteFile(hSerial, text_to_send + total_bytes_written,
size - total_bytes_written, &bytes_written, NULL))
{
fprintf(stderr, "Error writing text to %s\n", dev_name);
CloseHandle(hSerial);
exit(1);
}
// Wait for data back
if (read_back)
{
char buf[512];
DWORD readed = 0;
while (ReadFile(hSerial, buf, sizeof(buf), &readed, NULL))
{
if (readed == 0)
break;
}
}
total_bytes_written += bytes_written;
}
return total_bytes_written;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment