Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Visual C++/MFC/WTL > DeviceIoControl


Автор: Alexsar 28.9.2005, 23:42
Каким образом в VC работать с портами и дак-платами?
я знаю, что есть функция DeviceIoControl(бла, бла,..), но как её использовать?
мне необходимо посылать управляющие сигналы на платы и считывать выходные данные

пожалуйста, как можно подробней опишите работу этой функции, если есть пример, то буду очень благодарен smile

Автор: Coocky 29.9.2005, 10:21
[quote]
Интерфейс RS-232.... Так в официальной документации называется СОМ-порт компьютера. Современное оборудование (контроллеры, устройства сбора информации и т.д.) работают через этот порт. К любому модему, даже внутреннему, обращение происходит именно через СОМ-порт. А сколько существует внешних устройств, подключаемых по этому интерфейсу, сосчитать невозможно.

Работа с портами похожа на работу с файлами. Давайте рассмотрим простейший пример.

Код


void CCOMportDlg::OnBnClickedOpenportButton()
{
    if (hCom != INVALID_HANDLE_VALUE)
    {
        OnBnClickedButton1();
        Sleep(300);
    }

    char sPortName[10];
    cbPorts.GetWindowText(sPortName, 10);

    hCom = CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 
            0, NULL, OPEN_EXISTING, 0, NULL);

    if (hCom == INVALID_HANDLE_VALUE) 
        lLogList.AddString("Error opening port");
    else  
    {
        lLogList.AddString("Port successfully opened.");
        hThread = CreateThread(0, 0, ReadThread,
                (LPVOID)this, 0, 0);

        DCB dcb;
        GetCommState(hCom, &dcb);
        dcb.BaudRate = CBR_57600;
        dcb.ByteSize = 8;
        dcb.Parity = NOPARITY;
        dcb.StopBits = ONESTOPBIT; 
        if (SetCommState(hCom, &dcb))
            lLogList.AddString("Configuring OK");
        else
            lLogList.AddString("Configuring Error");
    }
}



Если попытаться открыть порт дважды, то будет получено сообщение об ошибке, поэтому первым делом нужно произвести эту проверку. И если порт открыт, то закрыть его. Эта проверка выполняется в функции onBnClickedButton1, которую я покажу чуть позже, и она будет вызываться при нажатии на кнопку Close port.

Теперь получим имя выбранного порта и откроем его. Для этого используется функция работы с простыми файлами CreateFile, только вместо имени файла указывается имя порта.

Если порт открыт удачно, то выводится соответствующее сообщение, и можно перейти к конфигурированию параметров соединения. Для этого сначала получите текущие настройки системы с помощью функции GetCommState. Ей нужно передать два параметра: указатель на открытый порт и указатель на структуру типа DCB. Эта структура содержит полную информацию о параметрах соединения и выглядит следующим образом:
Код



typedef struct _DCB {
    DWORD DCBlength;        // размер структуры DCB
    DWORD BaudRate;            // скорость передачи данных в бодах
    DWORD fBinary: 1;        // двоичный режим без проверки конца
                    // строки
    DWORD fParity: 1;        // включить проверку четность
    DWORD fOutxCtsFlow:1;        // CTS-правление потоком выхода
    DWORD fOutxDsrFlow:1;        // DSR-управление потоком выхода
    DWORD fDtrControl:2;        // DTR-тип управления потоком скорости
                    // передачи данных
    DWORD fDsrSensitivity:1;    // DSR-чувствительность
    DWORD fTXContinueOnXoff:1;    // стоп-сигнал продолжает выполнение
    DWORD fOutX: 1;            // старт/стоп-сигнал для управления
                    // выходящим потоком
    DWORD fInX: 1;            // старт/стоп - сигнал для управления
                    // входящим потоком
    DWORD fErrorChar: 1;        // включить проверку погрешностей
    DWORD fNull: 1;            // отвергать пустой поток данных
    DWORD fRtsControl:2;        // RTS - управление потоком данных
    DWORD fAbortOnError:1;        // проверять операции чтения/записи
    DWORD fDummy2:17;        // зарезервировано
    WORD wReserved;            // зарезервировано
    WORD XonLim;            // порог чувствительности старт-сигнала
    WORD XoffLim;            // порог чувствительности стоп-сигнала
    BYTE ByteSize;            // количество бит (обычно 7 или 8)
    BYTE Parity;            // четность байта
    BYTE StopBits;            // стоповые биты
    char XonChar;            // вид старт-сигнала в потоке
    char XoffChar;            // вид стоп-сигнала в потоке
    char ErrorChar;            // вид сигнала погрешности
    
    char EofChar;            // сигнал окончания потока
    char EvtChar;            // зарезервировано
    WORD wReserved1;        // зарезервировано
} DCB;


Если неправильно указаны параметры, то данные не будут передаваться и приниматься. Самое главное — заполнить следующие поля:

BaudRate — скорость передачи данных (бит/с). Указывается константа в виде CBR_скорость, где скорость должна быть равна скорости, поддерживаемой используемым устройством, например, 56000;
ByteSize — размер передаваемого байта (может быть 7 или 8);
Parity — флаг проверки четности;
StopBits — стоповые биты, могут принимать значения ONESTOPBIT (один), ONE5STOPBITS (полтора) или TWOSTOPBITS (два).

Остальные параметры можно оставить по умолчанию (те, которые вернула система). Но прежде чем указывать какие-либо параметры, обязательно прочитайте документацию на аппаратуру, с которой необходимо соединяться. Я не встречал устройств, которые поддерживали бы все режимы работы. Например, модем ZyXel Omni 56 K может поддерживать скорость от 2400 до 56 000, и можно указывать значения только из этого диапазона.

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

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

Код



DWORD __stdcall ReadThread(LPVOID hwnd)
{
    DWORD iSize;
    char sReceivedChar;
    while(true)
    {
        ReadFile(hCom,&sReceivedChar,1,&iSize,0);
        SendDlgItemMessage((HWND)hwnd,IDC_EDIT2,WM_CHAR,
            sReceivedChar,0);
    }
}


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

Теперь посмотрите на функцию закрытия порта, которая будет вызываться по нажатию кнопки Close port:
Код



void CCOMportDlg::OnBnClickedButton1()
{
    if (hCom == INVALID_HANDLE_VALUE)
        return;
        
    if (MessageBox("Close port?", "Warning", MB_YESNO) == IDYES)
    {
        TerminateThread(hThread, 0);
        CloseHandle(hCom);
        hCom = INVALID_HANDLE_VALUE;
    }
}


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

Если порт открыт, то выводится запрос на подтверждение закрытия порта. Если пользователь подтвердит, то прерывается поток, закрывается указатель порта и переменной hCom присваивается значение INVALID_HANDLE_VALUE.

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

Функция отправки данных в порт

Код


void CCOMportDlg::OnBnClickedSendcommandButton()
{
    if (hCom == INVALID_HANDLE_VALUE)
    {
        AfxMessageBox("Open port before send command");
        return;
    }

    char sSend[10224];
    eSendCommand.GetWindowText(sSend, 1024);

    if (strlen(sSend)>0)
    {
        lLogList.AddString(sSend);    

        sSend[strlen(sSend)] = '\r';
        sSend[strlen(sSend)] = '\0';

        TerminateThread(hThread,0);
        DWORD iSize;
        WriteFile(hCom, sSend, strlen(sSend), &iSize,0);
        hThread = CreateThread(0, 0, ReadThread,
            (LPVOID)this, 0, 0);
    }
}



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

Теперь прерываем поток чтения данных и записываем в порт данные стандартной функцией работы с файлами WriteFile. После записи можно снова запускать поток чтения.

Если у вас есть модем, то можете запустить программу и открыть порт, на котором настроен модем. Отправьте команду ATDTxxxxx, где хххх — это номер телефона. Модем должен будет начать набор указанного номера телефона.


Автор: Coocky 29.9.2005, 10:31
Надеюсь речь шла про СОМ порты.. smile

Автор: takedo 29.9.2005, 12:45
Coocky
Используя синхронное чтение, как в приведенном тексте, просто необходимо ещё настроить таймауты!!! А перед закрытием порта рекомендую на всякий случай вызвать PurgeComm., которая все почистит за собой.

Автор: Alexsar 1.10.2005, 00:06
TO Coocky: за COM порты спасибо smile
а что на счёт DeviceIoControl? smile

Автор: takedo 3.10.2005, 14:34
Coocky
TerminateThread(hThread, 0); - так закрывать не стоит, можешь получить вывод на экран диалога с предложением отправить уведомление об ошибке разработчику (то есть тебе). По началу я так же пытался закрываться - через раз вылетал по ошибке, поэтому лучше завести переменную или эвент. типа while(!quit){код}. А при выходе выполнять:
::InterLockedExchange(&quit,1);//можно и без интерлокед
WaitForSingleObject(Thread->m_hThread,timeout);
соответственно Thread->m_bAutoDelete = FALSE; при создании
smile smile
Я подумал, вдруг никто не знает... smile smile smile

Автор: Alexsar 4.10.2005, 17:17
В MSDN так:
DeviceIoControl
The DeviceIoControl function sends a control code directly to a specified device driver, causing the corresponding device to perform the specified operation.

BOOL DeviceIoControl(
HANDLE hDevice, // handle to device of interest
DWORD dwIoControlCode, // control code of operation to perform
LPVOID lpInBuffer, // pointer to buffer to supply input data
DWORD nInBufferSize, // size, in bytes, of input buffer
LPVOID lpOutBuffer, // pointer to buffer to receive output data
DWORD nOutBufferSize, // size, in bytes, of output buffer
LPDWORD lpBytesReturned, // pointer to variable to receive byte count
LPOVERLAPPED lpOverlapped // pointer to structure for asynchronous operation
);

как я понял hDevice нужно создать при помощи CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile
);

так вместо pFileName необходимо указать название устройства или путь к драйверу?
и ещё как преобразовать массив входных данных к указателю типа LPVOID?(хотя бы для ком порта)

Автор: Coocky 4.10.2005, 22:34
Цитата(Alexsar @ 4.10.2005, 17:17)
и ещё как преобразовать массив входных данных к указателю типа LPVOID

smile
Ненадо ничего преобразовывать, это означает , что ты можешь передать данные ЛЮБОГО типа

Автор: Alexsar 12.10.2005, 01:42
так что же pFileName, название устройства или путь к драйверу?

Автор: Coocky 12.10.2005, 10:06
Цитата(Alexsar @ 12.10.2005, 01:42)
так что же pFileName, название устройства или путь к драйверу?

А в МСДН слабо заглянуть? smile

Цитата

Pointer to a null-terminated string that specifies the name of the object, such as file, COM port, disk device, or console, to create or open

Видишь-ИЛИ, т.е. либо одно,либо другое либо третье..
А вот от того , что ты ввел с первом параметре, зависят другие параметры..
Цитата

The following list shows how CreateFile operates on files, communication resources, devices, services, and consoles:

Files
When creating a new file or truncating an existing file, the CreateFile function performs the following actions:

Combines the file attributes and flags specified by dwFlagsAndAttributes with FILE_ATTRIBUTE_ARCHIVE.
Sets the file length to zero.
CreateFile cannot be used to access files in the MODULES section of ROM. Modules are stored in a different format that applications cannot access. The only ROM files that can be accessed using CreateFile are those in the FILES section.
When opening an existing file, CreateFile ignores the file attributes specified by dwFlagsAndAttributes and sets the file length according to the value of dwCreationDisposition.

To store the maximum number of files on PC Card storage devices, limit file names to eight uppercase characters and file extensions to three uppercase characters. Also, do not allow non-OEM characters in file names. File names that do not conform to these limits require more than one physical directory entry on a PC Card.

Using the FILE_FLAG_RANDOM_ACCESS flag in the RAM file system, which places files in the object store, will prevent a file from being compressed. If performance is an issue, this could be the correct solution. Read and write operations to a compressed file are slower than read and write operations to an uncompressed file.

The OS does not support the concept of a current directory. If a path to a file is not supplied along with the file name, the OS will look for the file in the \Windows directory as well as in the root of the file system. To access a file in any other path, the application must supply the absolute path to the file. In some cases, the GetModuleFileName function can supply the working directory of the currently running executable file.

COM ports
The CreateFile function can create a handle to a COM port. By setting the dwCreationDisposition parameter to OPEN_EXISTING, read-only, write-only, or read/write access can be specified.

Devices
Volume handles may be opened as noncached at the discretion of the file system, even when the noncached option is not specified with CreateFile. You should assume that all Microsoft file systems open volume handles as noncached. The restrictions on noncached I/O for files also apply to volumes.

A file system may or may not require buffer alignment even though the data is noncached. However, if the noncached option is specified when opening a volume, buffer alignment is enforced regardless of the file system on the volume. Microsoft recommends that all file systems open volume handles as noncached and follow the noncached I/O restrictions.


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)