El concepto de puerto serie
El puerto serie se denomina interfaz serie. Las PC actuales generalmente tienen dos puertos serie, COM 1 y COM 2. El puerto serie se diferencia del puerto paralelo en que sus datos e información de control se transmiten bit a bit. Aunque será más lento, la distancia de transmisión es mayor que la del puerto paralelo, por lo que si desea comunicarse a una distancia mayor, debe utilizar el puerto serie. Por lo general, COM 1 usa un conector en forma de D de 9 pines, también llamado interfaz RS-232, mientras que COM 2 a veces usa el antiguo conector DB25-pin, también llamado interfaz RS-422.
Introducción a la comunicación serie
1. Principios básicos de la comunicación serie
La función esencial de un puerto serie es servir como conversor de código entre la CPU y los dispositivos serie. Cuando los datos se envían desde la CPU a través del puerto serie, los datos de bytes se convierten en bits serie. Al recibir datos, los bits en serie se convierten en datos de bytes.
En entorno Windows (Windows NT, Win98, Windows2000), el puerto serie forma parte de los recursos del sistema.
Si una aplicación desea utilizar el puerto serie para comunicarse, debe realizar una solicitud de aplicación de recursos al sistema operativo (abrir el puerto serie) antes de su uso. Una vez completada la comunicación, se deben liberar los recursos. (cierre el puerto serie).
El flujo del programa de comunicación serie es el siguiente:
2. Cómo conectar la línea de señal del puerto serie
Una interfaz RS-232C completa tiene 22 líneas y utiliza un enchufe estándar de 25 pines (o un enchufe de 9 pines). Las principales líneas de señal de 25 y 9 núcleos son las mismas. La siguiente introducción toma como ejemplo el RS-232C de 25 núcleos.
① Definición de la línea de señal principal:
Pin 2: Enviar datos TXD; Pin 3: Recibir datos RXD; Pin 4: Solicitar enviar RTS; Pin 5: Borrar para enviar CTS;
p>
Pin 6: Dispositivo de datos listo DSR; Pin 20: Terminal de datos listo DTR; Pin 8: Detección de portador de datos DCD;
Pin 1: Tierra de protección; 7: Tierra de señal.
②Características eléctricas:
La velocidad de transmisión de datos puede alcanzar hasta 20 K bps y la distancia máxima es de solo 15 m.
Nota: Después de leer MSDN 6.0 de Microsoft , es Windows La configuración de velocidad de los dispositivos de comunicación en serie (no necesariamente el puerto serie RS-232C, RS-422 o RS-449) en la API puede admitir hasta RS_256000, que es 256 K bps. ¡No sé qué dispositivo de comunicación en serie! ¿es? Pero pase lo que pase, la comunicación en serie entre el host general y el microcontrolador es principalmente de 9600 bps, lo que puede satisfacer las necesidades de comunicación.
③Aplicaciones típicas de interfaces:
La mayoría de los sistemas de aplicaciones informáticas y unidades inteligentes solo necesitan de 3 a 5 líneas de señal para funcionar. En este momento, además de TXD y RXD, es necesario utilizar líneas de señal como RTS, CTS, DCD, DTR y DSR. (Por supuesto, las líneas de señal correspondientes también deben configurarse en el programa).
Con el método de conexión anterior, al diseñar el programa, puede recibir y enviar datos directamente, y no es necesario Modificar las líneas de señal. El estado se juzga o establece. (Si la aplicación requiere el uso de señales de protocolo de enlace, etc., es necesario monitorear o configurar el estado de las líneas de señal correspondientes.
)
3. Una breve revisión de las aplicaciones de puerto serie de 16 bits
En aplicaciones de puerto serie de 16 bits, se utilizan las funciones de comunicación API de Windows de 16 bits:
① OpenComm() abre el puerto serie recurso y especifica la entrada, el tamaño del buffer de salida (en bytes);
CloseComm() cierra el puerto serie;
Ejemplo: int idComDev;
idComDev = OpenComm(" COM1", 1024, 128);
CloseComm(idComDev);
② BuildCommDCB() y setCommState() completan el bloque de control del dispositivo DCB, y luego configure los parámetros para el puerto serie abierto ;
Ejemplo: DCB dcb;
BuildCommDCB("COM1:2400,n,8,1", &dcb);
SetCommState(&dcb);
③ ReadComm y WriteComm() realizan operaciones de lectura y escritura en el puerto serie, es decir, reciben y envían datos.
Ejemplo: char *m_pRecieve ; int recuento;
ReadComm (idComDev,m_pRecieve,count);
Char wr[30]; int count2;
WriteComm(idComDev,wr,count2 );
Menos de 16 bits La característica más importante del programa de comunicación en serie es que la operación de dispositivos externos, como los puertos en serie, tiene sus propias funciones API únicas, mientras que el programa de 32 bits unifica las operaciones del puerto en serie (y; puertos paralelos, etc.) y operaciones de archivos, utilizando operaciones similares.
Cuatro. Las aplicaciones de puerto serie de 32 bits en MFC
Los programas de comunicación de puerto serie de 32 bits se pueden implementar de dos maneras: usando controles ActiveX; usando funciones de comunicación API.
Al usar controles ActiveX, la implementación del programa es muy simple y la estructura es clara. La desventaja es que es inflexible; las ventajas y desventajas de usar funciones de comunicación API son básicamente las opuestas.
Los siguientes son programas que agregan capacidades de comunicación serial a aplicaciones de documento único (SDI).
1 Uso de controles ActiveX:
El control MSComm proporcionado por VC++ 6.0 envía y recibe datos a través del puerto serie para proporcionar funciones de comunicación serie para aplicaciones. Es muy cómodo de usar, pero desafortunadamente hay muy poca información sobre el control MSComm.
⑴. Inserte el control MSComm en el espacio de trabajo actual.
Menú Proyecto------>Agregar al Proyecto---->Componentes y Controles----->Registrados
Controles ActiveX--->Seleccionar Componentes: Microsoft Communications Control,
versión 6.0 se inserta en el espacio de trabajo actual.
Como resultado, se agregó la clase CMSSomm (y los archivos correspondientes: mscomm.h y mscomm.cpp).
⑵. Agregue el control MSComm a MainFrm.h.
protegido:
CMSComm m_ComPort;
En Mainfrm.cpp::OnCreare():
DWORD style=WS_VISIBLE|WS_CHILD ;
if (!m_ComPort.Create(NULL,style,CRect(0,0,0,0),this,ID_COMMCTRL)){
TRACE0("Error al crear comunicaciones OLE Control ");
return -1; // no se puede crear
}
⑶. Inicialice el puerto serie
m_ComPort. SetCommPort (1); //¿Seleccionar COM?
m_ComPort.SetInBufferSize(1024); //Establecer el tamaño del búfer de entrada, Bytes
m_ComPortSize(512); /Establecer tamaño del buffer de entrada, Bytes//
if(!m_ComPort.GetPortOpen()) //Abrir el puerto serie
m_ComPort.SetPortOpen(TRUE);
m_ComPort.SetInputMode(1); //Establece el modo de entrada en modo binario
m_ComPort.SetSettings("9600,n,8,1");
m_ComPort.SetRTreshold(1); //1 significa que un carácter desencadena un evento
m_ComPort.SetInputLen(0);
⑷. Capture eventos del puerto serie. El control MSComm puede obtener datos del puerto mediante sondeo o métodos basados en eventos. Introducimos el método basado en eventos más utilizado: notificar al programa cuando hay un evento (como recibir datos). Estos eventos de comunicación deben capturarse y procesarse en el programa.
En MainFrm.h:
protegido:
afx_msg void OnCommMscomm();
DECLARE_EVENTSINK_MAP()
En MainFrm.cpp:
BEGIN_EVENTSINK_MAP(CMainFrame,CFrameWnd)
ON_EVENT(CMainFrame,ID_COMMCTRL,1,OnCommMscomm,VTS_NONE)
//Asignación de control ActiveX eventos
END_EVENTSINK_MAP()
⑸. Lectura y escritura del puerto serie La función para completar la lectura y escritura es realmente muy simple, solo GetInput () y SetOutput (). Los prototipos de las dos funciones son:
VARIANT GetInput(); y void SetOutput(const VARIANT& newValue); ambos usan el tipo VARIANT (todos los parámetros y valores de retorno de Idispatch::Invoke se tratan internamente). como manejo de objetos VARIANT).
Ya sea cuando la PC lee los datos cargados o cuando la PC envía comandos de enlace descendente, estamos acostumbrados a usar la forma de cadena (que también se puede decir que es la forma de matriz). Consulte la documentación de VARIANT para saber que puede usar BSTR para representar una cadena, pero desafortunadamente todos los BSTR contienen caracteres anchos, incluso si no definimos _UNICODE_UNICODE. WinNT admite caracteres anchos, pero Win95 no.
Para resolver los problemas anteriores, utilizamos CbyteArray en el trabajo real y damos la parte correspondiente del programa de la siguiente manera:
void CMainFrame::OnCommMscomm(){
VARIANT vResponse; int k;
if(m_commCtrl.GetCommEvent()==2) {
k=m_commCtrl.GetInBufferCount() //Número de caracteres recibidos
; if(k >0) {
vResponse=m_commCtrl.GetInput(); //leer
SaveData(k,(unsigned char*) vResponse.parray->pvData);
} // Cuando se reciben caracteres, el control MSComm envía un evento}
. . . . . // Maneja otros controles de MSComm
}
void CMainFrame::OnCommSend() {
. . . . . . . . // Prepara el comando a enviar y ponlo en TxData[]
CByteArray array;
array.RemoveAll();
array.SetSize(Count ) ;
for(i=0;i
array.SetAt(i, TxData[i]);
m_ComPort.SetOutput(COleVariant(array) ) ; // Enviar datos
}
Preste especial atención a los contenidos en ⑷ y ⑸, que son los puntos clave y difíciles en el trabajo real
El segundo uso. Funciones de comunicación API de 32 bits:
A muchos amigos les puede resultar extraño: usar funciones API de 32 bits para escribir un programa de comunicación en serie no significa reemplazar la API de 16 bits por una de 16 bits. ¿Programa de comunicación en serie de bits? Pero mucha gente lo ha estudiado hace muchos años...
Este artículo principalmente quiere presentar cómo combinar comunicación sin bloqueo, subprocesos múltiples y otros métodos en la comunicación en serie API para escribir. programas de comunicación de alta calidad Es más práctico cuando las tareas de procesamiento de la CPU son pesadas y hay una gran cantidad de datos de comunicación con dispositivos periféricos
⑴ Defina variables globales en MainFrm.cpp
.HANDLE hCom. // Mango del puerto serie a abrir
HANDLE hCommWatchThread; // Función global del hilo auxiliar
⑵ Abre el puerto serie y configura. el puerto serie
hCom =CreateFile ( "COM2", GENERIC_READ | GENERIC_WRITE, // Permitir lectura y escritura
0, // Este debe ser 0
NULL, // sin atributos de seguridad
OPEN_EXISTING, //Establece el método de generación
FILE_FLAG_OVERLAPPED, //Vamos a utilizar comunicación asíncrona
NULL );
Tenga en cuenta que utilizamos la estructura FILE_FLAG_OVERLAPPED. Esta es la clave para utilizar API para lograr una comunicación sin bloqueo.
ASSERT(hCom!=INVALID_HANDLE_VALUE); //Compruebe si la operación de apertura del puerto serie fue exitosa
SetCommMask(hCom, EV_RXCHAR|EV_TXEMPTY);//Establezca el tipo controlado por eventos
SetupComm( hCom, 1024,512) ; //Establece el tamaño de los buffers de entrada y salida
PurgeComm( hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
| PURGE_RXCLEAR ); /Borrar los buffers de entrada y salida
COMMTIMEOUTS CommTimeOuts; //Definir la estructura del tiempo de espera y completar la estructura
…………
SetCommTimeouts (hCom, &CommTimeOuts);//Establecer el tiempo de espera permitido para operaciones de lectura y escritura
DCB dcb; //Definir la estructura del bloque de control de datos
GetCommState(hCom, &dcb); /Leer la configuración de parámetros del puerto serie original
dcb.BaudRate =9600; dcb.ByteSize =8; dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;dcb.fBinary = TRUE;dcb.fParity = FALSE;
SetCommState(hCom, &dcb); //Configuración de parámetros del puerto serie
La estructura COMMTIMEOUTS y DCB mencionados anteriormente son muy importantes y se necesitan parámetros. ser cuidadosamente seleccionado en el trabajo real.
⑶ Inicie un hilo auxiliar para procesar eventos del puerto serie.
Windows proporciona dos tipos de subprocesos, subprocesos auxiliares y subprocesos de interfaz de usuario. La diferencia es: el hilo secundario no tiene ventana, por lo que no tiene su propio bucle de mensajes. Pero los subprocesos de trabajo son fáciles de programar y, a menudo, útiles.
Esta vez utilizamos un hilo secundario. Se utiliza principalmente para monitorear el estado del puerto serie para ver si han llegado datos y si hay errores de comunicación, mientras que el hilo principal puede concentrarse en tareas importantes como el procesamiento de datos y proporcionar una interfaz de usuario amigable.
hCommWatchThread=
CreateThread( (LPSECURITY_ATTRIBUTES) NULL, //Atributos de seguridad
0, //Inicializa el tamaño de la pila de subprocesos, el valor predeterminado es igual que el hilo principal Mismo tamaño
(LPTHREAD_START_ROUTINE)CommWatchProc, //Función global del hilo
GetSafeHwnd(), //El identificador del marco principal se pasa aquí p>
0 , &dwThreadID );
ASSERT(hCommWatchThread!=NULL);
(4) Escriba una función global para que el subproceso auxiliar complete principalmente el trabajo de datos recepción. Tenga en cuenta el uso de la estructura SUPERPUESTA y cómo se logra la comunicación sin bloqueo.
UINT CommWatchProc(HWND hSendWnd){
DWORD dwEvtMask=0 ;
SetCommMask( hCom, EV_RXCHAR|EV_TXEMPTY );//Qué eventos del puerto serie necesitan ser monitoreado?
WaitCommEvent( hCom, &dwEvtMask, os ); // Esperar a que ocurran eventos de comunicación del puerto serie
Detectar el dwEvtMask devuelto para saber qué eventos del puerto serie ocurrieron:
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ // Los datos llegan al búfer
COMSTAT ComStat ; DWORD dwLength;
ClearCommError(hCom, &dwErrorFlags, &ComStat ) ;
dwLength = ComStat.cbInQue; //¿Cuántos datos hay en el búfer de entrada?
if (dwLength > 0) {
BOOL fReadStat;
fReadStat = ReadFile( hCom, lpBuffer, dwLength, &dwBytesRead;
&READ_OS( npTTYInfo ) ); //Leer datos
Nota: Usamos FILE_FLAG_OVERLAPPED en CreareFile(), y ahora ReadFile() también debe usar la estructura LPOVERLAPPED. De lo contrario, la función informará incorrectamente que la operación de lectura. se ha completado.
Usando la estructura LPOVERLAPPED, ReadFile() regresa inmediatamente sin esperar a que se complete la operación de lectura, logrando una comunicación sin bloqueo
En este momento, ReadFile(). ) devuelve FALSO, GetLastError() devuelve ERROR_IO_PENDING.
if (!fReadStat){
if (GetLastError() == ERROR_IO_PENDING){
while(! GetOverlappedResult(hCom,
&READ_OS( npTTYInfo ), & dwBytesRead, TRUE )){
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE) continuar
//Los datos del buffer no han sido leídos, continuar
…… ……
::PostMessage((HWND)hSendWnd,WM_NOTIFYPROCESS,0 , 0);// Notificar al hilo principal que el puerto serie ha recibido datos}
La llamada comunicación sin bloqueo, es decir, comunicación asincrónica. Significa que cuando se realizan operaciones de lectura y escritura de datos que requieren mucho tiempo (no solo operaciones de comunicación en serie), una vez que se llama a ReadFile() y WriteFile(), pueden regresar inmediatamente, dejando que las operaciones de lectura y escritura reales se ejecuten en el fondo. Por el contrario, si se utiliza la comunicación de bloqueo, la operación de lectura o escritura debe completarse antes de que pueda regresar. El problema surge porque la operación puede tardar un tiempo arbitrariamente largo en completarse.
Las operaciones muy bloqueantes también permiten realizar operaciones de lectura y escritura al mismo tiempo (es decir, ¿operaciones superpuestas?), Lo cual es muy útil en el trabajo real.
Para usar comunicación sin bloqueo, primero debe usar FILE_FLAG_OVERLAPPED en CreateFile(); luego el parámetro lpOverlapped no debe ser NULL en ReadFile(), luego verifique el valor de retorno de la llamada a la función, llame a GetLastError( ) y consulte Si se debe devolver ERROR_IO_PENDING. Si es así, la llamada final a GetOverlappedResult() devuelve el resultado de la operación superpuesta. WriteFile() se utiliza de manera similar.
⑸. Envíe comandos posteriores en el hilo principal.
BOOL fWriteStat; char szBuffer[count];
…………//Prepara los datos a enviar y ponlos en szBuffer[]
fWriteStat = WriteFile(hCom, szBuffer, dwBytesToWrite,
&dwBytesWritten, &WRITE_OS( npTTYInfo ) ); //Escribir datos
Nota: Usamos FILE_FLAG_OVERLAPPED en CreareFile(), ahora WriteFile() También se debe usar la estructura LPOVERLAPPED. De lo contrario, la función informará incorrectamente que la operación de escritura se ha completado.
Al usar la estructura LPOVERLAPPED, WriteFile() regresa inmediatamente sin esperar a que se complete la operación de escritura, logrando no hacerlo. -bloqueando la comunicación En este momento, WriteFile() devuelve FALSE, GetLastError() devuelve ERROR_IO_PENDING.
int err=GetLastError();
if (!fWriteStat) {
if(GetLastError () == ERROR_IO_PENDING){
while(!GetOverlappedResult(hCom, &WRITE_OS( npTTYInfo ),
&dwBytesWritten, TRUE )) {
dwError = GetLastError( );
if(dwError == ERROR_IO_INCOMPLETE){
// resultado normal si no está terminado
dwBytesSent += dwBytesWritten; continuar }
........................
En resumen, utilizamos subprocesos múltiples Tecnología para monitorear el puerto serie en el subproceso auxiliar. Cuando llegan los datos, se controla por eventos, lee los datos y los informa al subproceso principal (los datos se envían en el subproceso principal, en términos relativos, los datos de los comandos posteriores). siempre mucho menos); y WaitCommEvent(), ReadFile(), WriteFile() utilizan tecnología de comunicación sin bloqueo y dependen de operaciones de lectura y escritura superpuestas para permitir que las operaciones de lectura y escritura del puerto serie se ejecuten en segundo plano.
Confiando en las ricas funciones de vc6.0 y combinando las tecnologías que mencionamos, podemos escribir una aplicación de comunicación en serie con poderosas capacidades de control. Personalmente prefiero la tecnología API porque los métodos de control son mucho más flexibles y las funciones mucho más potentes.