RS232. Взгляд изнутри

Последовательный порт (далее ПП) удобный инструмент для общения между разными периферийными устройствами (как собранные самостоятельно на основе какого-нибудь МК, так и заводские: принтеры, осциллографы и т.д.) с одной стороны, и ПК с другой. На сегодняшний день наиболее популярные из всех ПП являются RS232 стандарт (переводится как «Recommended Standard») за его простоту и USB стандарт («Universal Serial BUS») за его резвость.
USB бесспорно вещь полезная, но жудко навороченная. Поскольку многим самодельным устройствам бешенный обмен данными с ПК неособо нужон, тогда на помощи приходит простой, надежный и многоопытный RS232 Интерфейс.

Нус Преступим.

По RS232 стандарту устройства участвующие в обмене данными бывают двух типов:
Data Terminal Equipment (DTE) (устройство отдающее команды — ведущий) и
Data Circuit-Terminating Equipment (DCE) (периферия, обслуживающая хозяина — ведомый). Нередко, некоторые периферийные устройства ведут себя как DTE (например осциллографы, или наши с вами девайсы).

Типы соединения

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



В случае когда устройств только два, или есть явный ведущий которого слушаются все остальные, никакого посредника им не нужно, а это означает что к их общению больше никто не подключится, и никакого арбитра в лице модема им не надо ( в отличие от предыдущего типа соединения, когда к одному принтеру можно подключить штук 10 ПК ). Опять-же главное недопустить одновременной отправки данных — в определенный момент времени, общатся может только одна пара устройств. Такое соединение называется нуль-модемное соединение:



Типы передач данных

Минимальное количество проводков необходимое для обмена данными равно двум (этокий жадный изврат), если передача является односторонней ([Tx, GND]). В случае когда необходимо полноценное — двухстороннее общение число проводков возростает аж до трех ([Rx, Tx, GND]). Большинство периферийных устройств поддерживают одновременную передачу и прием данных — full-duplex, но если один из собеседников на такое не способен, обмен переходит в разряд неполноценных — half-duplex (пока один не закончил передачу/прием другой пляшит под его дудку).

Распиновка COM разъёма





В столбце Signal Name, DATA Terminal можно заменить на ПК (то есть Data Terminal Ready соответствует ПК готов к работе), а DATA Set на Периферия.

Как следует из предыдущей таблицы, все пины делятся на управляющие (control pins) и транспортные (Data pins). Каждый пин в определенный момент времени может находьтся только в одном из двух состояний: активном (on) или неактивном (off). Чтобы не запутатся, и както защитить данные от помех, разработчики решили что во время передачи данных они были сначало усилены (+5В –> +12В, 0В –> -12В ) а потом инвертированы, в то время как c управляющими сигналами они долго не парились и просто их усилели (тоесть положительное стало еще положительней а отрицательное — отрицательнее, относительно общего провода).



Назначение управляющих пинов ([RTS, CTS], [DTR, DSR] и [CD, RI]) сводится к следующему:

• Отслеживать состояние собеседника
• Отслеживать поток данных

Пара [RTS, CTS] — используется для обозначения готовности данной пары устройств к передачи/приему соответственно.

Пример:

1. DTE устройство устанавливает RTS = on, сигнализируя о том что оно готово к приему данных. Если устройство получило достаточное количество данных то устанавливаем RTS =off.
2. DCE устройство устанавливает CTS =on, сигнализируя о том что оно готово к приему данных. Если устройство получило достаточное количество данных то устанавливаем CTS =off.

Кто каким пином будет управлять (тоесть кому быть DTE а кому DCE) решать вам. Соответственно программы управления этими устройствами должны выставить RTS(выход)/CTS (вход), или наоборот, иначе могут быть глюки.

Пара [DTR, DSR] — большинство устройств используют эти пины для сигнализирования что они подключены и готовы к работе.

Пример:

1. DTE устройство устанавливает DTR=on, сообщая DCE устройству что оно готово к работе. Соответственно когда DTE устанавливает DTR=off, то оно больше не желает (или не может) общатся (положила трубку :) )
2. DCE устройство устанавливает DSR=on, сообщая что оно подключено, а когда DSR=off – оно отключено.

Такой метод контроля потока данных называется – hardware handshaking (чтото вроде аппаратное управление). Пары [DTR, DSR] и [RTS, CTS] могут быть с легкостью взаимо-заменены без всякого ущерба.

Пара [CD, RI] – используется для обозначения (в тот самом случае когда один принтер на отару кампов) что в данный момент линии передачи данных кем-то заняты.
Как правило этой парой управляет модем, но не обязательно.

Photobucket

где:

• St – Стартовый Бит (начало передачи данных) – логический ноль
• 0..8 – позиция бита (данных) в пакете (позиция «0» – LSB)
• P – бит парности (проверка успешной передачи данных)
• Sp1,Sp2 – стоп биты (завершают передачу пакета) – логическая единица
• [] – в скобках обозначены биты которые могут отсутствовать
(биты данных с 5 по 8 так или иначе будут переданы, но не рассмотрены — мусор)
• IDLE – ожидание (логическая единица)

Как я уже говорил, во время передачи — данные инвертируются, так что если будете проверять осциллографом как отсылается пакет — не пугайтесь.

Часто формат пакета обозначается следующим образом: 8-N-1 (8 бит данных, без бита проверки, один стоп бит) или 5-E-2 (5 бит данных (3 бита мусора), с проверкой на четность, два стоп бита).

Реализация



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

Софт
UPD: заменил вывод cout на printf, и убрал флаги RxClear и TxClear

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

• Открыть порт (Opening a Port)
• Конфигурация порта (Configuring a Serial Port)
• Конфигурация временных интервалов (Configuring Timeouts)
• Запись данных (Writing to a Serial Port)
• Чтение данных (Reading from a Serial Port)
• Использование прерываний (Using Communication Events)
• Закрыть порт (Closing a Serial Port)

Также много интересного можно узнать на следующих сайтах: Programming Serial Connections , Serial programming in win32 OS



#include <windows.h> /* В данной библиотеке находится описяние ПП */
#include <cstdlib>
#include <iostream>
using namespace std;
//-----------------------
 HANDLE hPort; /* обиект управляющий ПП */
 DWORD dwMask; /* переменная в которой хронится информация об прерывании по которому сработал ПП */ 
 static char ReadBuff; /* переменная хронящая прочитаный символ * /
 static DWORD rBytes,tBytes; /* переменные хронящие число прочитанных/отправленных байтов */
 char name[5]; /* хронит имя ПП - пример: COM3 */

//-----------------------
// закрыть COM порт
//-----------------------
void CloseComPort(HANDLE& port)
{
 PurgeComm(port, PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR);
 if(port != INVALID_HANDLE_VALUE) // if comm port is not closed
  CloseHandle(port);
 else //---
  printf("Communication is closed with errors\n");
 port = INVALID_HANDLE_VALUE;
}

//-----------------------
// определить формат пакета
//-----------------------
bool ComPortSettings(unsigned baud)
{
 DCB PortDCB;
 PortDCB.DCBlength = sizeof (DCB); // Get the default port setting information.
 GetCommState (hPort, &PortDCB);
// Change the DCB structure settings.
//-----------------------
 PortDCB.BaudRate = baud; // Current baud 
 PortDCB.fBinary = TRUE;  // Binary mode
 PortDCB.ByteSize = 8;    // Number of bits/byte, 4-8 
 PortDCB.Parity = NOPARITY;  // 0-4 = no,odd,even,mark,space 
 PortDCB.StopBits = ONESTOPBIT;  // 0,1,2 = 1, 1.5, 2 
//-----------------------
if (!SetCommState (hPort, &PortDCB))
 {
  CloseComPort(hPort);
  printf("Unable to configure the serial port\n");
  return false;
 }
 else return true;
}

//-----------------------
// определить временные задержки
//-----------------------
bool ComPortTimeouts(void)
{
 COMMTIMEOUTS CommTimeouts;
//---
 GetCommTimeouts (hPort, &CommTimeouts);
 CommTimeouts.ReadIntervalTimeout = MAXDWORD; // aproximativelly 50 days
 CommTimeouts.ReadTotalTimeoutMultiplier = 0;
 CommTimeouts.ReadTotalTimeoutConstant = 0;
 CommTimeouts.WriteTotalTimeoutMultiplier = 0;
 CommTimeouts.WriteTotalTimeoutConstant = 0;
//---
 if (!SetCommTimeouts (hPort, &CommTimeouts))
 {
  CloseComPort(hPort);
  printf("Unable to set the timeout parameters\n");
  return false;
 }
 else return true;
}

//-----------------------
// открыть COM порт
//-----------------------
bool OpenComPort(const char* name,unsigned baud)
{//--- Open Communication Port ---
 hPort=CreateFile(name,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
 if (hPort != INVALID_HANDLE_VALUE) // if comm port is opened
  if(
     ComPortSettings(baud) &&   // setting DCB structure
     ComPortTimeouts()     &&   // setting commtimouts structure
     SetCommMask(hPort, EV_RXCHAR) // init RX event
    ) // execute comm port settings
  {
   printf("Comm Port will be opened successfully\n");
   return true;
  }
// error settings
 printf("COMM PORT небыл инициализирован или он не существует\n");
 return false;
}

//-----------------------
// отправить байт
//-----------------------
void SendByte(const char& send)
{
 printf("Write:  ");
 if(WriteFile(hPort,&send,1,&tBytes,NULL)) printf("%c\n",send);
/*
  В функции WriteFile: 
                      &send - адресс переменной где хронятся данные преднозначенные для отправки 
                      цифра "1" - количество байт которых нужно отослать
                      &tBytes - адресс переменной где хронится информация об количестве отправленных байт (в случае успешной отправки)
*/
 else 
 {
  printf("error\n");
  PurgeComm(hPort, PURGE_TXABORT );
/* очистить ПП от ошибок */
 }
}

//-----------------------
// принять байт
//-----------------------
void ReceiveByte(char& byte)
{
 printf("Read:   ");
 // SetCommMask(hPort, EV_RXCHAR); // init RX event, перебросил в OpenComPort
 while(!WaitCommEvent (hPort, &dwMask, 0));
 if(ReadFile(hPort,&byte, 1,&rBytes, NULL)) printf("%c\n",byte);
/*
  В функции ReadFile: 
                      &byte - адресс переменной куда нужно записать прочитанные данные 
                      цифра "1" - количество байт которых нужно прочитать
                      &rBytes - адресс переменной где хронится информация об количестве прочитанных байт (в случае успешного приема)
*/
 else 
 {
  printf("error\n");
  PurgeComm(hPort, PURGE_RXABORT ); 
/* очистить ПП от ошибок */
 }
}



Запихните предыдущий код в хидэр фаил, например с именем COM_INIT.h и можно использовать ПП.


#include <stdio.h>
#include "COM_INIT.h"

int main(int argc, char *argv[])
{
 printf("COM port: ");
 gets(name); /* имя COM порта с COM1 до COM9 */ 
//-----------------------
  if(OpenComPort(name,CBR_9600)) /* открыть COM порт */
  {
   ReceiveByte(ReadBuff); /* принять данные */
   SendByte('H'); /* послать данные */
   CloseComPort(hPort); /* закрыть COM порт */
  }
  else printf("communication error\n");
//-----------------------
 system("pause");
 return EXIT_SUCCESS;
}


Надеюсь эти скромные знания кому-то помогут. Если есть вопросы попытаюсь ответить.

Комментарии (28)

RSS свернуть / развернуть
Вроде-бы что нового можно узнать о таком простом интерфейсе? Но даже тут кое-что для себя почерпнул.
Спасибо!
+1
… вам спасибо за комент
0
… на это и надеялся :)
0
Кстати, сие примеры кода чем компилить? MS VS или можно где угодно, например в билдере?
0
Насколько я вижу — в любом С++. А если оторвать некоторые фишки (скажем, вывод через cout заменить на printf или что там в С) — то и С.
0
Я использую DevC++… должно работать и под VS
0
MinGW другими словами, он же GCC для виндов. Dev-C++ — IDE него (и вроде еще нескольких компилеров), довольно симпатичная (и что забавно — на Delphi).
0
:) парадокс… с другой стороны — не писать же C компилятар на C компиляторе… хотя кто знает. Кстати насчет Buildera неуверен… там если не ошибаюсь другие библиотеки… с небольшими изменениями и там пойдет
serial-programming-in-win32-os.blogspot.com/2008/07/serial-communication-with-borland-c.html
0
Не парадокс. МинГВ собой и компилируется. Тем более Dev-C++ не компилятор, а IDE. Не парадокс впрочем и выбор Delphi для нее — это очень удобный инструмент для лепления гуев и вообще программ с сложным GUI.
В билдере/BCC собраться должно, хотя он увешан расширениями как елочка игрушками — со стандартом он совместим, и афайк весьма неплохо.
0
у меня такой код в билде нормально себя чуствует (ну не один в один, но подобный).
В билдере правда лучше писать оконные, а для них полезно задать:
0
CommTimeouts.ReadIntervalTimeout = 0;
что есть отключение блокирования на функции чтения. функция чтения возвращается сразу с тем что есть, даже если порт не принял ни одного байта. Полезно ибо программа не будет зависать при сбоях в работе устройства :)

P.S.: блин, кто +сделал оправкой комментария? второй раз лажаю :)
0
… если писать на билдере то тогда уж лучше использовать прерывания и паралельные процессы… кстати у меня функция чтения работает по прерывания, как раз для того чтоб не возвращать пустату
+1
Ага, и что бы человек, который с трудом под винду пишет однопоточное приложение начал писать многопоточное? Хватит, насмотрелись :)
Для устройств запрос-ответ многопоточность лишнее. Гораздо проще отпарил запрос — получил ответ. Ну если не получил, продуплил в Application->ProcessMessage() и опять на чтение. И гуй не зависает, и у пользователя есть возможность отменить действие и нет запарок с многопоточностью.
Для устройств самостоятельно посылающих данные тоже ничего сложного, таймеры никто не отменял.
И заметьте что бросить компонент таймена на форму дело элементарное и аналогичное помещению других компонентов. А вот создание класса потока уже не ограничивается простым тыканьем мышки.
0
Вот как раз для софт для работы с устройствами не лишне сделать многопоточным (если это не тупой гуй для каких-то целей). При том, что реализуется это не сложно, и, кстати, использование готовых классов потоков гораздо удобней, чем чистые API (это я я про VCL).
+1
Неплохая статейка, как раз в дипломке есть вероятность описания RS232 85%)можно что-то почерпнуть)
0
Зачем очищать буферы приёма и передачи после приёма и передачи каждого байта? Что если в буфере было несколько байт? Потеря. Очищать буферы нужно только при обнаружении ошибки обмена, факт наличия ошибки проверяется функцией ClearCommError, она возвращает количество байт в буферах приема и передачи, код ошибки и что-то еще и сбрасывает флаг ошибки. Ее нужно вызывать перед каждым вызовом WriteFile и ReadFile и смотреть что из нее вернулось, если есть ошибка обмена, тогда позвать PurgeComm.
0
Сей пример не расчитам для какой-либо проверки. PurgeComm не чистит буфер данных, он сбрасывает флаги сигнализирующие об ошибках, которые запрещают прием/передачу до выяснения обстоятельств. Если этого не зделать ПП зависнит.
0
… упс скузи :)… флаги RxClear и TxClear как раз чистят буфер. Дело вкуса — можно убрать эти два флага, или убрать например только TxClear для функции приёма, или RxClear — для записи.
0
Вот именно, поэтому лучше позвать ClearCommError и посмотреть что случилось.
0
По работе с портами под win есть отличная статья Олега Титова «Работа с коммуникационными портами (COM и LPT) в программах для Win32».

Что касается статьи, то лично я от название «RS232. Взгляд изнутри» ожидал, что будет более детальное расмотрение RS232 вплоть до программной реализации. Не сочтите за кртитику)
0
… по началу хотел дать другое название — чтото вроде введения, но описывать весь протокол это будет большая статья, а когда читаешь статью а она все не заканчивается, это тоже неочень, поэтому решил описать ключевые моменты (которые в других источниках както нитак поданы), а кому нужно больше и понел о чем идет речь — дальше может читать любую статью, не запутается
0
Я именно обратил внимание, что содержание статьи и название не совсем согласуются. А как «введение» — то что нужно)
0
Вот спасибо огромное автору — в частности, за идею чтения COM-порта. Вторую ночь бился: передать микроконтроллеру команду из своей управляющей программы передал, а как получить подтверждение (мало ли чего-спал он там, пока я ему команды давал), догадаться никак не мог. Главное — в тырнете масса примеров, да не аппликабельными они в моем применении оказались. А тут — ясно, доходчиво, а главное — РАБОТАЕТ! :)
0
… :), эт хорошо
0
… для лучшего понимания ПП в Windows, рекомендую почитать Allen Denver: Serial Communications in Win32
0
Премного благодарен, материал весьма и весьма полезный.
0
Добрый день.
У меня задача:
Есть весы CAS rw2601p. В них есть порт RS232. Мне надо получить Ворд(Exel, txt) файл с данными и потом распечатать.
Я прочитал статью: RS232. Взгляд изнутри. И попробовал реализовать, но не получилось. Как можно решить задачу? В каком формате сохранить скрипт? Пробовал в программе dev c++
0
Добрый день.
У меня задача:
Есть весы CAS rw2601p. В них есть порт RS232. Мне надо получить Ворд(Exel, txt) файл с данными и потом распечатать.
Я прочитал статью: RS232. Взгляд изнутри. И попробовал реализовать, но не получилось. Как можно решить задачу? В каком формате сохранить скрипт? Пробовал в программе dev c++
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.