Выключаем Intel ME 11, используя недокументированный режим

▍ основы ввода-вывода в linux: всё есть файл

Принцип операционных систем типа Unix (и GNU Linux вместе с ними): всё есть файл. Файл может быть регулярным на диске, к которому мы все привыкли, файлом может быть канал (именованный или не именованный) для передачи данных, передача данных по сети, тоже по сути, работа с файл-сокетом (только не именованным). Таким образом, разобравшись с функциями работы с файлами, мы частично разберёмся с работой СОМ-порта.

Файл можно: создать, открыть, закрыть, удалить, прочитать и записать в файл. Всё это системные вызовы в ядро, для работы с данными, которые хранятся или передаются с помощью файла. Поскольку создание и удаление файла нас не интересует, далее их не рассматриваю.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
//Либо 
int open(const char *pathname, int flags, mode_t mode); //открыть файл

Принимает на вход имя файла, флаги опции открытия, и, если расширенная функция, то и права доступа. Но нас это тоже пока не волнует. Возвращает дескриптор (описатель) открытого файла, число больше нуля. В любом другом случае – это ошибка открытия файла.

#include <unistd.h>
int close(int fd); //закрыть файл


Закрывает файл, на вход принимает дескриптор файла. Возвращает нуль в случае успеха.

Самые интересные системные вызовы для нас — это чтение и запись.

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

read()

пытается записать

count

байтов файлового описателя

fd

в буфер, адрес которого начинается с

buf


Если количество

count

равно нулю, то

read(

) возвращает это нулевое значение и завершает свою работу. Если

count

больше, чем

SSIZE_MAX

, то результат не может быть определён.

При успешном завершении вызова возвращается количество байтов, которые были считаны (нулевое значение означает конец файла), а позиция файла увеличивается на это значение. Если количество прочитанных байтов меньше, чем количество запрошенных, то это не считается ошибкой: например, данные могли быть почти в конце файла, в канале, на терминале, или

read()

был прерван сигналом. В случае ошибки возвращаемое значение равно -1, а переменной errno присваивается номер ошибки. В этом случае позиция файла не определена.

❒ Обратите внимание! Количество запрашиваемых байт на чтение может не соответствовать реальному количеству считанных байт. Оно будет меньше, либо равно запрошенному.

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

write

записывает до

count

байтов из буфера

buf

в файл, на который ссылается файловый дескриптор

fd


В случае успешного завершения возвращается количество байтов, которые были записаны (ноль означает, что не было записано ни одного байта). В случае ошибки возвращается -1, а переменной

errno

присваивается соответствующее значение.

❒ Следует помнить, что запись байт не гарантирует, что все байты будут записаны. Следует также контролировать количество байт, которое было записано, и в цикле дописывать их до окончания.

С точки зрения ядра, функции read и write осуществляют копирование данных из ядра в пространство пользователя (для чтения), и копирование из пространства пользователя в ядро (в случае записи). Таким образом, важно понимать, что функция записи не гарантирует реальную запись в устройство, а только копирование во внутренние буфера ядра.

▍ как сделать неблокирующее чтение?

Неблокирующее чтение отличается от блокирующего чтения тем, что если в буфере ядра linux нет данных, то функция read не будет ожидать их получения и сразу вернёт управление программе. С одной стороны, это очень удобно, но с другой, если вы ожидаете посылку, то вам придётся в цикле читать, пока не получите данные.

В своём вебинаре о работе с СОМ-портом (ссылки будут ниже), я разбирал пример чтения карт-ридера магнитных карт. Там у меня уже готовая библиотека работы с СОМ-портом. Её мы и разберём. Пример обитает тут.

Не буду разбирать подробно всю программу, пробегусь по основным моментам. Остальное в ней всё достаточно очевидно, и многое мы уже разобрали. Всё будет в файле uart.c.Чтобы перевести СОМ-порт в неблокирующий режим, после открытия порта, нам необходимо файловый дескриптор перевести в режим неблокирующего чтения. Это делается с помощью функции.

fcntl(fd, F_SETFL, FNDELAY);

Таким образом, теперь, мы будем выходить сразу, после функции read, вне зависимости есть у нас данные или нет.

Сейчас стоит задача ожидать данные в буфере ядра, и если их нет то выходить по какому-то таймауту, возвращая управления программе. Для этого напишем следующую функцию.

int read_com(int fd, int len , int timeout, uint8_t * buff){
        int ret = 0;
        
        struct pollfd fds;
        fds.fd=fd;
        fds.events = POLLIN;
        poll(&fds, 1, timeout);
        if(fds.revents & POLLIN) {
                ret = read(fd, buff, len);
        }
        if(ret<0){
                ret = 0;
        }
        return ret; 
}

На вход функция принимает файловый дескриптор открытого порта, длину запрашиваемых данных, таймаут в миллисекундах, после которого возвращается управление системе и указатель на буфер, куда будут считаны данные.

Управление осуществляется с помощью структуры:

struct pollfd fds;

В неё мы записываем файловый дескриптор, который хотим мониторить.

fds.fd=fd;


Указываем, то что мы хотим мониторить событие получение данных:

fds.events = POLLIN;

И, соответственно, взводим наш «сторожевой таймер».

poll(&fds, 1, timeout);

Из этой функции мы выйдем либо по таймауту, либо по получению данных.

Дальше мы проверяем событие, которое произошло. И если событие соответствует POLLIN, то производим чтение данных.

И после чего возвращаем количество считанных данных или нуль в случае ошибки или нуля данных. Это не очень корректно, так как всё же лучше обработать все возникающие ошибки, а не отправлять нуль. Но это тестовый пример и можно так сделать.

Аналогичная функция есть и для записи. Для чего это нужно, ведь мы просто копируем данные в ядро? Да очень просто, если у вас режим передачи RS-485 управляется линией DTR/RTS, либо GPIO, то вы должны точно знать, когда данные были отправлены, чтобы правильно выставить эти пины, меняя режим передачи на приём. Выглядит это всё следующим образом.

int write_com(int fd, uint8_t * buf, size_t size, int timeout){
        int ret = 0;

        struct pollfd fds;
        fds.fd=fd;
        fds.events = POLLOUT;
        
        poll(&fds, 1, timeout);
        if(fds.revents & POLLOUT){
#ifdef USE_RTS
                set_rts(fd,RTS_SET);
#endif
                ret = write(fd, (uint8_t*)buf, size);

                tcdrain(fd); 
#ifdef USE_RTS
                set_rts(fd,RTS_CLR);
#endif
        }
        
        if(ret!=size) return 0;
        return 1; 
}

Здесь

poll

настроен на передачу. Единственное, на что стоит обратить внимание, это на вызов функции

tcdrain(fd);

Эта функция будет ожидать, пока все данные вывода, записанные на объект, на который ссылается

fd

, не будут переданы. Функцию

int set_rts(int fd, int on)

мы разберём в следующей главе.

▍ простейший пример блокирующего чтения из сом-порта

Разберём пример блокирующего чтения из COM-порта был взят

и немного модифицирован. Все исходники будут доступны в

к этой статье.

Блокирующее чтение, как следует из названия, блокирует программу в системном вызове read до тех пор, пока не появятся данные, которые может получить read. Если данных нет, то этот вызов будет ждать вечно и ничего работать не будет. Этот режим работы наиболее прост для понимания работы с СОМ-портом, и с него лучше всего начинать работать.

Разберём основные части кода SerialPort_read.c:

Открытие файл-устройства:

fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY);

Обратите внимание на флаги открытия: O_RDWR – открываем для чтения и записи, O_NOCTTY – терминал не может управлять данным процессом (тот терминал, с которого мы запускаем это приложение, может управлять процессом, например, послать сигнал при комбинации ctrl-c).

struct termios SerialPortSettings;
tcgetattr(fd, &SerialPortSettings);

Инициализируем структуру termios и получаем текущие значения структуры.

cfsetispeed(&SerialPortSettings,B9600); 
cfsetospeed(&SerialPortSettings,B9600);

Задаём скорость на чтение и на запись. Обратите внимание, что скорости на чтение и на запись могут быть разными. Также скорость задаётся макросами, начинающиеся с символа “B” (они описаны в termios.h). Могут принимать следующие значения:

        B0
        B50
        B75
        B110
        B134
        B150
        B200
        B300
        B600
        B1200
        B1800
        B2400
        B4800
        B9600
        B19200
        B38400
        B57600
        B115200
        B230400

Нулевая скорость, B0, используется для завершения связи.

:/>  Полезные команды терминального сервера (Terminal Server) » SERGEY STRAKHOV

Далее идут стандартные настройки порта:

SerialPortSettings.c_cflag &= ~PARENB;

Отключаем бит чётности (если флаг очищен).

SerialPortSettings.c_cflag &= ~CSTOPB;

Если флаг установлен, то стоп-бит равен двум, если очищен (в этом случае), то равен одному.

SerialPortSettings.c_cflag &= ~CSIZE;


Очищаем маску размера данных.

SerialPortSettings.c_cflag |=  CS8;

Устанавливаем размер передаваемых данных, равный восьми битам.

SerialPortSettings.c_cflag &= ~CRTSCTS;

Отключаем аппаратное управление потоком данных (RTS/CTS).

SerialPortSettings.c_cflag |= CREAD | CLOCAL;

Включаем приёмник, игнорируем контрольные линии модема.

SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);


Отключаем управление потоком данных при вводе и выводе, отключаем возможность символов запускать ввод.

12 Программная реализация UART.

Таблица 3. Регистры UART.

адресDLABчтение/записьНазвание регистра
00h0WRTHR(Transmit Holding Register)-регистр данных ожидающих передачи
00h0RDRBR(Receiver Buffer Register)- буферный регистр приемника
00h1RD/WRDLL(Divisor Latch LSB)-младший байт делителя частоты
01h1RD/WRDIM(Divisor Latch MSB)-старший байт делителя частоты
01h0RD/WRIER(Interrupt Enable Register)-регистр разрешения прерывания
02hхRDIIR(Interrupt Identification Register)-регистр идентифицирующий прерывания
02hхWRFCR(FIFO Control Register)-регистр управления режимом FIFO
03hxRD/WRLCR(Line Control Register)-регистр управления линией связи
04hxRD/WRMCR(Modem Control Register)-регистр управления модемом
05hxRD/WRLSR(Line Status Register)-регистр состояния линии связи
06hxRD/WRMSR(Modem Status Register)-регистр состояния модема
07hxRD/WRSCR(Scratch Pad Register)-регистр временного хранения

Рис.13 Функциональная схема UART PC16550.

THR-регистр данных ожидающих передачи(только для записи) (Transmit Holding Register)

Рис.14 Регистр THR (Адрес=00h, DLAB=0, WR)

RBR- буферный регистр приемника(только для чтения) (Receiver Buffer Register)

Рис.15 Регистр RBR (Адрес=00h, DLAB=0, RD)

DLL-младший байт делителя частоты :16 (чтение/запись) (Divisor Latch LSB)

Рис.16 Регистр RBR (Адрес=00h, DLAB=1, RD/WR)

&nbsp &nbsp &nbsp &nbsp В это регистре находится младший байт делителя частоты деленного на 16.

DIM-старший байт делителя частоты :16 (чтение/запись) (Divisor Latch MSB)

Рис.17 Регистр RBR (Адрес=01h, DLAB=1, RD/WR)

&nbsp &nbsp &nbsp &nbsp В этом регистре находится старший байт делителя частоты деленного на 16. В микросхеме UART частота задающего кварца делится на делитель частоты(Decimal Divisor),который получается из двухбайтового числа (DIM,DLL) умноженного на 16.

Таким образом делитель частоты задает скорость обмена данных через UART. Записью в регистры DIM и DLL старшего и младшего байта этого двухбайтового числа вы зададите скорость обмена СОМ-порта в бит/сек. Для кварца UART частотой f=1,8432 МГц, делитель частоты:16 считается по формуле:

D=115200/V, где V-скорость в бит/сек, D=делитель частоты:16

Для кварца UART частотой f=24 МГц, делитель частоты:16 считается по формуле:

D=1 500 000/V, где V-скорость в бит/сек, D=делитель частоты:16

Таблица 4. Делитель частоты для UART PC16550.

1,8432 МГц24 МГц
Скорость, бит/секделитель:16DIMDLLделитель:16DIMDLL
50230409h00h3000075h30h
75153606h00h200004Eh20h
110104741h07h1363635h44h
15076803h00h1000027h10h
30038401h80h500013h88h
60019200hC0h250009hC4h
1 2009600h60h125004hE2h
1 8006400h40h83303h41h
2 0005800h3Ah75002hEEh
2 4004800h30h62502h71h
3 6003200h20h4170hA1h
4 8002400h18h31201h38h
7 2001600h10h20800hD0h
9 6001200h0Ch15600h9Ch
14 400800h08h10400h68h
19 200600h06h7800h4Eh
28 800400h04h5200h34h
38 400300h03h3900h27h
57 600200h02h2600h1Ah
115 200100h01h1300h0Dh
250 000xxx600h06h
300 000xxx500h05h
375 000xxx400h04h
500 000xxx300h03h
750 000xxx200h02h
1 500 000xxx100h01h

Как видно из таблицы 4, СОМ порт ПК (с UART 16550 и выше) может работать на скорости до 1,5Mb/s.

IER-регистр разрешения прерывания(чтение/запись) (Interrupt Enable Register)

Рис.18 Регистр IER (Адрес=01h, DLAB=0, RD/WR)

&nbsp &nbsp &nbsp &nbsp Регистр разрешения прерываний дает разрешения определённым событиям вызывать прерывание микропроцессора. Бит 0. RxD_IЕ — если RxD_IЕ=1,то разрешено прерывание для приема данных,это прерывание возникает когда необходимо принять символ из регистра RBR (в режиме FIFO — прерывание по тайм-ауту).

Бит 1. TxD_IE — если TxD_IEЕ=1,то разрешено прерывание для передачи данных, это прерывание возникает когда передающий буфер пуст и необходимо загрузить байт в регистр THR.

Бит 2. RxL_IЕ — если RxL_IЕ=1,то разрешено прерывание при обрыве линии связи или ошибке в приёме данных, это прерывание возникает когда в регистре состояния линии связи LSR будут выставлены биты этих ошибок.

Бит 3. Mod_IЕ — если Mod_IЕ =1,то разрешено прерывание при изменении состояния любого из входных сигналов RST,CTS,DCD,RI, это прерывание возникает когда состояние входных сигналов COM-порта изменились.

Бит 4..7. Не используются и всегда равны 0.

IIR-регистр идентифицирующий прерывания (чтение) (Interrupt Identification Register)

Рис.19 Регистр IIR (Адрес=02h, RD)

Бит 0. IP(Interrupt Pending)— если IP=1, то все прерывания обработаны. Если IP=0,то есть необработанные прерывания.

Бит 1. I_ID0(Interrupt ID Bit0)- нулевой бит идентификатора прерываний Бит 2. I_ID1(Interrupt ID Bit1)- первый бит идентификатора прерываний Бит 3. I_ID2(Interrupt ID Bit2)- второй бит идентификатора прерываний

Таблица 5. Идентификация прерывания (обычный режим)

I_ID2I_ID1I_ID0Приоритетидентификация
x00ЧетвертыйИзменилось состояние модема, сбрасывается прочтением регистра MSR.
x01ТретийРегистр THR пуск, ожидается байт от CPU. Сбрасывается записью байта в THR.
x10ВторойПринят байт данных в регистр RBR, сбрасывается чтением регистра RBR.
x11НаивысшийОбрыв линии или ошибка на линии, сбрасывается прочтением регистра LSR.

2.1. Программирование СОМ-порта с помощью API функций Windows.

Попробуйте, создать в проводнике папку или файл с именем «СОМ1», сделать это не получится. ОС Windows зарезервировала имена от СОМ1 до СОМ9 для работы с СОМ-портами.

Рассмотрим подробнее программирование СОМ-порта с помощью API-функций:

1. Для работы с СОМ-портом первое что надо сделать, это открыть порт. Сделать это можно с помощью API функции CreateFile из библиотеки «kernel32» : Эта функция создает новый объект и присваивает ему описатель, по которому с этим объектом можно будет работать. Пример описания функции CreateFile на языке Си:

Пример декларирования функции CreateFile на языке VB6:

Declare Function CreateFile Lib «kernel32» Alias «CreateFileA» (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long

Пример открытия СОМ1 в VB6:

Com_Handle = CreateFile(«COM1:», &HC0000000, 0, 0&, &H3, 0, 0)

Пример открытия СОМ1 в Си:

Com_Handle = CreateFile(«COM1», GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

2. После открытия СОМ порта можно передавать и принимать данные через этот СОМ-порт. Для передачи данных используется API функция WriteFile из библиотеки kernel32.Для приёма данных используется API функция ReadFile из библиотеки kernel32.

Пример описания функции ReadFile и WriteFile на языке Си: Пример декларирования функции ReadFile и WriteFile на языке VB6:

Declare Function ReadFile Lib «kernel32» (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As Long) As Boolean

Declare Function WriteFile Lib «kernel32» (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As Long) As Boolean

С помощью этих функций из программного кода можно читать или записывать данные в указанный буфер. Пример чтения 255 байт из порта в массив на языке VB6:

Dim File_Buffer(255) As Byte ‘приемный буфер Dim Com_Byte_Read As Long ‘количество принятых байт Dim Retval As Boolean Retval = ReadFile(Com_Handle, File_Buffer(0), 255, Com_Byte_Read, 0)

3. После окончания работы с портом его нужно закрыть. Закрытие порта осуществляется API функцией CloseHandle из библиотеки kernel32.

:/>  Как установить Windows 10 второй системой

Пример описания функции CloseHandle на языке Си: Пример декларирования функции CloseHandleна языке VB6:

Declare Function CloseHandle Lib «kernel32» (ByVal hObject As Long) As Boolean

Пример закрытия порта на языке VB6:

Dim Com_Exit as Boolean Com_Exit = CloseHandle(Com_Handle)

4. Настройка режима работы сом порта осуществляется с помощью структур данных, которые представляют из себя набор переменных разного типа. Структуруы загружаются и читаются с помощью API функций. Рассмотрим основные структуры для настройки режимов работы сом порта:

Структура DCB определяет основные настройки СОМ порта. В ней содержиться реальная информация из регистров UART. Для работы с DCB структурой используют API функции из библиотеки kernel32.:

BuildCommDCB— заполняет указанную структуру DCB значениями, заданными в строке управления устройством. Строка управления устройством использует синтаксис команды mode MS-DOS. SetCommState— конфигурирует коммуникационное устройство согласно данным указанным в структуре DCB.

Данная структура задает временные параметры(задержки и таймауты) работы СОМ порта и определяет поведение функций ReadFile и WriteFile. Для работы с COMMTIMEOUTS структурой используют API функции из библиотеки kernel32.:

SetCommTimeouts— устанавливает параметры простоя для всех операций чтения и записи для заданного коммуникационного устройства. GetCommTimeouts— извлекает данные о параметрах простоя для всех операций чтения и записи на заданном коммуникационном устройстве.

Структура которая сообщает статус СОМ порта после обнаружения ошибки связи. Для работы с COMMSTAT структурой используют API функции из библиотеки kernel32.:

ClearCommError— Функция ClearCommError извлекает информацию об коммуникационной ошибке и сообщает о текущем состоянии коммуникационного устройства. Функция вызывается тогда, когда происходит ошибка обмена информацией и сбрасывает флажок ошибки устройства, чтобы включить в работу дополнительные операции ввода и вывода данных (I/O).

Структура которая сообщает информацию о свойствах коммуникационного устройства. Для работы с COMMPROP структурой используют API функции из библиотеки kernel32:

Программирование

Первая строка определяет местоположение имен, к которому принадлежат определяемые нами классы. В проекте можно использовать несколько файлов, и вам не нужно объединять их со словом include, которое часто встречается в других языках. Достаточно, чтобы все файлы в проекте имели одно и то же местоположение.

Затем у нас есть название нашего основного класса и его конструктора, который содержит функцию InitializeComponent. Он создает окно, которое мы видим после компиляции программы.

Вопрос в том, что это за волшебная функция, которая не принимает никаких параметров и точно знает, какое окно она должна создать для нас. Ответ на этот вопрос скрыт под ключевым словом partial и в другом файле нашего проекта — Form1.Designer.cs.

Если мы откроем его, мы также увидим определение нашего класса со словом partial в пространстве (местоположении) имен TutorialCOM. Это, конечно, вторая часть нашего названия основного класса. Здесь находится функция InitializeComponent, которая автоматически обновляется, когда мы что-то добавляем в конструктор.

В начале файла кода мы можем найти строки, которые начинаются со слова using. Они информируют компилятор о стандартных пространствах имен, которые мы будем использовать в нашей программе.

Те, которые в настоящее время сгенерированы в нашем файле, относятся к классам, используемым в дизайнере. Что означает использование using? Проще всего это проиллюстрировать на примере.

Для нашей программы нам понадобится пространство имен System.IO.Ports, где есть класс SerialPort, который обрабатывает последовательный порт. Нам нужно добавить элемент указанного выше класса в нашу программу, чтобы установить соединение. Внутри класса мы можем определить переменную:

System.IO.Ports.SerialPort port;

Если мы хотим продолжим ссылаться на элементы этого пространства имен, то каждый раз заново должны писать System.IO.Ports. Кстати, после ввода точки мы видим, что Visual Studio отображает раскрывающийся список с доступными элементами для выбора, что значительно облегчает написание.

using System.IO.Ports;

Теперь наше определение переменной может быть сокращено до:

SerialPort port;

С этого момента нам не нужно каждый раз писать полный путь. Ключевое слово using позволяет компилятору понимать, где искать классы, которые мы используем. У нас добавлена ​​переменная типа SerialPort. Теперь нам нужно инициализировать его в конструкторе следующим кодом:

Мы изменим параметры подключения в соответствующей вкладке. И нам нужно убедиться, что там отображаются правильные значения, как текущие, так и доступные в раскрывающихся списках. Для этого нам нужно создать обработчик событий, который будет запускаться всякий раз, когда мы будем переходить на вкладку параметров. Для этого в конструктор добавляем событие:

и ниже функция, которая его поддерживает:

Options.Enter — это событие, назначенное объекту с именем options и запускаемое каждый раз, когда мы открываем эту вкладку. Каждый раз, когда происходит это событие, все обработчики событий (то есть функции) в нем выполняются.

Вы можете добавить обработчик события = или вычесть — = для события. Однако в функции обработки событий, помимо простого присвоения Strings текстам полей, у нас есть интересная структура foreach. Она используется для работы со всеми возможными элементами, которые могут быть найдены в данном наборе, особенно, когда мы еще не знаем, сколько их будет.

В нашем случае, мы добавляем в список все элементы String, найденные в наборах имен портов, параметров четности и параметров остановки. Таким образом, если мы что-то подключим к пяти COM-портам, список будет включать 5 пунктов, а если мы ничего не подключим, список будет пустым. Это чрезвычайно полезная команда.

Если мы скомпилируем программу сейчас, мы сможем легко протестировать выпадающие списки. Перед использованием foreach мы очищаем содержимое списков, чтобы избежать дублирования или устаревания значений. Важно сначала использовать конструкцию foreach, а затем назначать тексты, т.к. функция Clear удаляет содержимое списка, включая текущее значение.

Если мы включим программу, скомпилированную на этом этапе, то увидим, что, если у нас открыта вкладка параметров, и что-то вставлено в порт, изменение не будет замечено, пока мы не выйдем и не войдем повторно в программу.

Вот почему мы добавили кнопку «Обновить» , с помощью которой мы сможем немедленно обновить эти параметры. Если мы откроем конструктор и дважды щелкнем на кнопку «Обновить», весь код для обработки события после нажатия кнопки будет сгенерирован автоматически.

и замените его на:

Теперь давайте создадим событие нажатия кнопки по умолчанию, чтобы оно автоматически изменяло значения на все, что мы захотим:

С другой стороны, для кнопки «Отмена» пусть принимаются те же значения, что и при активации вкладки:

У нас есть готовая вкладка с параметрами. Мы можем выбрать свойства подключения из списка, а нажатие кнопки запускает соответствующее действие. Теперь по порядку на вкладке «Терминал». Красный квадрат используется для установления соединения. Для этого создайте событие и поместите туда следующий код:

На первый взгляд это может показаться трудной работой, но мы сейчас разберем все досконально.

:/>  Ошибка 0x80070005 отказано в доступе в Windows 8.1 | Настройка серверов windows и linux

Если порт открыт, мы завершаем соединение и устанавливаем окно изображения и метку как в начале. Однако, если порт закрыт, и мы хотим его открыть, мы должны учитывать возможность ошибок. Вот почему мы используем структуру try-catch.

Сначала выполняются команды в скобках try. Если программа обнаруживает ошибку, она переходит в раздел перехвата и отображает сообщение об ошибке на экране. Если бы этот фрагмент отсутствовал, программа бы не работала. Если она без проблем выполняет все содержимое скобки try, то пропускает часть catch и продолжает работу.

Содержимое скобки try сначала состоит из перезаписи значения порта из опций, открытия порта и установки цвета квадрата на зеленый, а текста — на информацию об активном соединении. Функция также отправляет на терминал соответствующее сообщение о состоянии оранжевого цвета. Для этого предназначена функция AddColor, к которой мы еще вернемся.

Создание приложения

Во-первых, давайте рассмотрим, как должно выглядеть наше приложение, и что оно должно вообще делать. Этот этап проектирования значительно упрощается благодаря конструктору и возможности предварительного просмотра. Вам обязательно понадобится панель для настройки соединения и способа отправки и получения сообщений.

На первых этапах имеет смысл сделать простой терминал с двумя текстовыми полями. Один будет отображать отправленные сообщения, а другой — полученные сообщения, или одно текстовое поле для всех сообщений. Позже можно будет расширить функциональные возможности для передачи данных в соответствии с некоторым более продвинутым протоколом, например, для непрерывного получения данных от датчиков робота, чтения содержимого EEPROM или для управления сервоприводами.

Наше приложение будет состоять из трех вкладок — терминал общего назначения, протокол связи с конкретным микроконтроллером и вариант подключения.

Давайте увеличим наше окно. В опциях вы также можете изменить имя, текст и размер шрифта для облегчения работы в дальнейшем и улучшения эстетики. Затем, найдя его на панели инструментов в группе Containers TabControl, давайте добавим его в наше окно.

В опциях Tab найдите параметр Dock и установите для него значение Fill. После этого он заполнит все окно. Затем найдите параметр «Tab Pages» и нажмите кнопку «…». Там мы добавляем третью вкладку и меняем имя и отображаем текст, как показано на картинке ниже:

Название и текст второй таблицы мы пока не меняли. Сначала мы сделаем терминал, а вкладку зарезервируем для более изощренного способа связи. Вы же можете использовать только вкладки с параметрами и терминалом, т.к. управлять вкладками очень просто и требует только использования кнопок Add и Remove. Теперь переходим на вкладку параметров и добавляем такие элементы, как label, combobox и button.

Не забывайте изменить поля «Text» и «Name» в параметрах. Мы меняем имена на такие, чтобы было понятно, для чего предназначен каждый элемент.

Для ComboBox, связанных со скоростью передачи и количеством битов данных, элементы этого набора должны быть определены вручную. Найдите опцию «Items» и нажмите на точки рядом со словом «Collection». Здесь мы вводим значения от 5 до 9 для битов данных, каждое из которых разделяется вводом. Во втором вводим все стандартные скорости передачи, то есть:

После создания вкладки опций, аналогичной изображенной на картинке, мы можем перейти к проектированию терминала. Наш выглядит так:

Он состоит из двух кнопок. Красный квадрат будет кнопкой начала / завершения и одновременно покажет текущий статус.

В большом текстовом поле будут показаны отправленные / полученные данные в шестнадцатеричном коде. В маленьком числовом поле вы сможете выбрать значение нового байта для отправки в шестнадцатеричном коде.

Нажатие кнопки «Отправить» отправит текущее значение через COM-порт, а «Очистить» очистит журнал действий. Следует помнить о некоторых настройках параметров. В PictureBox мы устанавливаем BackColor → Red , в RichTextBox мы устанавливаем ReadOnly →True, чтобы случайно не изменить содержимое логотипа.

В NumericUpDown мы меняем параметр Hexadecimal на true, чтобы числа отображались в шестнадцатеричном формате, а минимальные и максимальные значения на 0 и 255 соответственно, чтобы они помещались в один байт.

Таким образом, нам удалось завершить этап «проектирования» нашего приложения. До сих пор все было просто и не имело ничего общего с настоящим программированием. Наше приложение уже выглядит неплохо, и после компиляции оно даже получило некоторые функции, такие как выбор байтового значения или возможность выбора некоторых параметров из раскрывающегося списка.

Следующим шагом будет добавление функций к соответствующим кнопкам и реализация последовательного подключения. Но мы будем делать это обычным программированием, а не перетаскиванием элементов и изменением их свойств.

Техническая документация. работа с com портом в visual c – исходники с , исходники visual c

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //
(LPTSTR) &lpMsgBuf,
0,
NULL);

if(dwRC && lpMsgBuf)
{
sprintf(sMsg, “COM open failed: Port=%s Error=%d – %s”,
m_sComPort, dwError, lpMsgBuf);
AfxMessageBox(sMsg);
}
else
{
sprintf(sMsg, “COM open failed: Port=%s Error=%d “,
m_sComPort, dwError);
AfxMessageBox(sMsg);
} // end if
if(dwRC && lpMsgBuf)
{
LocalFree( lpMsgBuf );
} // end if

} // end if

if(m_bPortReady)
{
m_bPortReady = SetupComm(m_hCom,
128, 128); //

if(!m_bPortReady)
{
dwError = GetLastError();
sprintf(sMsg, “SetupComm failed: Port=%s Error=%d”,
m_sComPort, dwError);
AfxMessageBox(sMsg);

} // end if
} // end if

if(m_bPortReady)
{
m_bPortReady = GetCommState(m_hCom, &m_dcb);
if(!m_bPortReady)
{
dwError = GetLastError();
sprintf(sMsg, “GetCommState failed: Port=%s Error=%d”,
m_sComPort, dwError);
AfxMessageBox(sMsg);
} // end if
} // end if

if(m_bPortReady)
{
m_dcb.BaudRate = 9600;
m_dcb.ByteSize = 8;
m_dcb.Parity = NOPARITY;
m_dcb.StopBits = ONESTOPBIT;
m_dcb.fAbortOnError = TRUE;

m_bPortReady = SetCommState(m_hCom, &m_dcb);
if(!m_bPortReady)
{
dwError = GetLastError();
sprintf(sMsg, “SetCommState failed: Port=%s Error = %d”,
m_sComPort, dwError);
AfxMessageBox(sMsg);
}
} // end if

if(m_bPortReady)
{
m_bPortReady = GetCommTimeouts (m_hCom, &m_CommTimeouts);
if(!m_bPortReady)
{
dwError = GetLastError();
sprintf(sMsg, “GetCommTimeouts failed: Port=%s Error = %d”,
m_sComPort, dwError);
AfxMessageBox(sMsg);
} // end if
} // end if

if(m_bPortReady)
{
m_CommTimeouts.ReadIntervalTimeout = 50;
m_CommTimeouts.ReadTotalTimeoutConstant = 50;
m_CommTimeouts.ReadTotalTimeoutMultiplier = 10;
m_CommTimeouts.WriteTotalTimeoutConstant = 50;
m_CommTimeouts.WriteTotalTimeoutMultiplier = 10;
m_bPortReady = SetCommTimeouts (m_hCom, &m_CommTimeouts);
if(!m_bPortReady)
{
dwError = GetLastError();
sprintf(sMsg, “SetCommTimeouts failed: Port=%s Error = %d”,
m_sComPort, dwError);
AfxMessageBox(sMsg);
} // end if
} // end if

return m_bPortReady;
} // end CComPort::Initialize

//
//

Оставьте комментарий

Adblock
detector