mirror of
https://github.com/stargieg/bacnet-stack
synced 2025-10-26 23:35:52 +08:00
Changed info prints to go to stdout rather than stderr to clean up Wireshark extcap usage. ........
631 lines
18 KiB
C
631 lines
18 KiB
C
/*####COPYRIGHTBEGIN####
|
|
-------------------------------------------
|
|
Copyright (C) 2004 Steve Karg
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
The Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307
|
|
USA.
|
|
|
|
As a special exception, if other files instantiate templates or
|
|
use macros or inline functions from this file, or you compile
|
|
this file and link it with other works to produce a work based
|
|
on this file, this file does not by itself cause the resulting
|
|
work to be covered by the GNU General Public License. However
|
|
the source code for this file must still be made available in
|
|
accordance with section (3) of the GNU General Public License.
|
|
|
|
This exception does not invalidate any other reasons why a work
|
|
based on this file might be covered by the GNU General Public
|
|
License.
|
|
-------------------------------------------
|
|
####COPYRIGHTEND####*/
|
|
|
|
/** @file win32/rs485.c Provides Windows-specific functions for RS-485 */
|
|
|
|
/* Suggested USB to RS485 devices:
|
|
B&B Electronics USOPTL4
|
|
SerialGear USB-COMi-SI-M
|
|
USB-RS485-WE-1800-BT
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include "mstp.h"
|
|
#include "dlmstp.h"
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define STRICT 1
|
|
#include <windows.h>
|
|
#include "rs485.h"
|
|
#include "fifo.h"
|
|
|
|
/* details from Serial Communications in Win32 at MSDN */
|
|
|
|
/* Win32 handle for the port */
|
|
HANDLE RS485_Handle;
|
|
/* Original COM Timeouts */
|
|
static COMMTIMEOUTS RS485_Timeouts;
|
|
/* COM port name COM1, COM2, etc */
|
|
static char RS485_Port_Name[256] = "COM4";
|
|
/* baud rate - MS enumerated
|
|
CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400,
|
|
CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400,
|
|
CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000 */
|
|
static DWORD RS485_Baud = CBR_38400;
|
|
/* ByteSize in bits: 5, 6, 7, 8 are valid */
|
|
static DWORD RS485_ByteSize = 8;
|
|
/* Parity - MS enumerated:
|
|
NOPARITY, EVENPARITY, ODDPARITY, MARKPARITY, SPACEPARITY */
|
|
static DWORD RS485_Parity = NOPARITY;
|
|
/* StopBits - MS enumerated:
|
|
ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS */
|
|
static DWORD RS485_StopBits = ONESTOPBIT;
|
|
/* DTRControl - MS enumerated:
|
|
DTR_CONTROL_ENABLE, DTR_CONTROL_DISABLE, DTR_CONTROL_HANDSHAKE */
|
|
static DWORD RS485_DTRControl = DTR_CONTROL_DISABLE;
|
|
/* RTSControl - MS enumerated:
|
|
RTS_CONTROL_ENABLE, RTS_CONTROL_DISABLE,
|
|
RTS_CONTROL_HANDSHAKE, RTS_CONTROL_TOGGLE */
|
|
static DWORD RS485_RTSControl = RTS_CONTROL_DISABLE;
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Change the characters in a string to uppercase
|
|
* RETURN: nothing
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void strupper(
|
|
char *str)
|
|
{
|
|
char *p;
|
|
for (p = str; *p != '\0'; ++p) {
|
|
*p = (char) toupper(*p);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Initializes the RS485 hardware and variables, and starts in
|
|
* receive mode.
|
|
* RETURN: none
|
|
* ALGORITHM: none
|
|
* NOTES: expects a constant char ifname, or char from the heap
|
|
*****************************************************************************/
|
|
void RS485_Set_Interface(
|
|
char *ifname)
|
|
{
|
|
/* For COM ports greater than 9 you have to use a special syntax
|
|
for CreateFile. The syntax also works for COM ports 1-9. */
|
|
/* http://support.microsoft.com/kb/115831 */
|
|
if (ifname) {
|
|
strupper(ifname);
|
|
if (strncmp("COM", ifname, 3) == 0) {
|
|
if (strlen(ifname) > 3) {
|
|
sprintf(RS485_Port_Name, "\\\\.\\COM%i", atoi(ifname + 3));
|
|
fprintf(stdout, "Adjusted interface name to %s\r\n",
|
|
RS485_Port_Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Check the serial port to see if port exists
|
|
* RETURN: true if port exists
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool RS485_Interface_Valid(
|
|
unsigned port_number)
|
|
{
|
|
HANDLE h = 0;
|
|
DWORD err = 0;
|
|
bool status = false;
|
|
char ifname[255] = "";
|
|
|
|
sprintf(ifname, "\\\\.\\COM%u", port_number);
|
|
h = CreateFile(ifname, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
|
OPEN_EXISTING, 0, NULL);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
err = GetLastError();
|
|
if ((err == ERROR_ACCESS_DENIED) || (err == ERROR_GEN_FAILURE) ||
|
|
(err == ERROR_SHARING_VIOLATION) || (err == ERROR_SEM_TIMEOUT)) {
|
|
status = true;
|
|
}
|
|
} else {
|
|
status = true;
|
|
CloseHandle(h);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
const char *RS485_Interface(
|
|
void)
|
|
{
|
|
return RS485_Port_Name;
|
|
}
|
|
|
|
void RS485_Print_Error(
|
|
void)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) & lpMsgBuf, 0, NULL);
|
|
MessageBox(NULL, lpMsgBuf, "GetLastError", MB_OK | MB_ICONINFORMATION);
|
|
LocalFree(lpMsgBuf);
|
|
|
|
return;
|
|
}
|
|
|
|
static void RS485_Configure_Status(
|
|
void)
|
|
{
|
|
DCB dcb = { 0 };
|
|
COMMTIMEOUTS ctNew;
|
|
|
|
|
|
dcb.DCBlength = sizeof(dcb);
|
|
/* get current DCB settings */
|
|
if (!GetCommState(RS485_Handle, &dcb)) {
|
|
fprintf(stderr, "Unable to get status from %s\n", RS485_Port_Name);
|
|
RS485_Print_Error();
|
|
exit(1);
|
|
}
|
|
|
|
/* update DCB rate, byte size, parity, and stop bits size */
|
|
dcb.BaudRate = RS485_Baud;
|
|
dcb.ByteSize = (unsigned char) RS485_ByteSize;
|
|
dcb.Parity = (unsigned char) RS485_Parity;
|
|
dcb.StopBits = (unsigned char) RS485_StopBits;
|
|
|
|
/* update flow control settings */
|
|
dcb.fDtrControl = RS485_DTRControl;
|
|
dcb.fRtsControl = RS485_RTSControl;
|
|
/*
|
|
dcb.fOutxCtsFlow = CTSOUTFLOW(TTYInfo);
|
|
dcb.fOutxDsrFlow = DSROUTFLOW(TTYInfo);
|
|
dcb.fDsrSensitivity = DSRINFLOW(TTYInfo);
|
|
dcb.fOutX = XONXOFFOUTFLOW(TTYInfo);
|
|
dcb.fInX = XONXOFFINFLOW(TTYInfo);
|
|
dcb.fTXContinueOnXoff = TXAFTERXOFFSENT(TTYInfo);
|
|
dcb.XonChar = XONCHAR(TTYInfo);
|
|
dcb.XoffChar = XOFFCHAR(TTYInfo);
|
|
dcb.XonLim = XONLIMIT(TTYInfo);
|
|
dcb.XoffLim = XOFFLIMIT(TTYInfo);
|
|
// DCB settings not in the user's control
|
|
dcb.fParity = TRUE;
|
|
*/
|
|
if (!SetCommState(RS485_Handle, &dcb)) {
|
|
fprintf(stderr, "Unable to set status on %s\n", RS485_Port_Name);
|
|
RS485_Print_Error();
|
|
}
|
|
/* configure the COM port timeout values */
|
|
ctNew.ReadIntervalTimeout = MAXDWORD;
|
|
ctNew.ReadTotalTimeoutMultiplier = MAXDWORD;
|
|
ctNew.ReadTotalTimeoutConstant = 1000;
|
|
ctNew.WriteTotalTimeoutMultiplier = 0;
|
|
ctNew.WriteTotalTimeoutConstant = 0;
|
|
if (!SetCommTimeouts(RS485_Handle, &ctNew)) {
|
|
RS485_Print_Error();
|
|
}
|
|
/* Get rid of any stray characters */
|
|
if (!PurgeComm(RS485_Handle, PURGE_TXABORT | PURGE_RXABORT)) {
|
|
fprintf(stderr, "Unable to purge %s\n", RS485_Port_Name);
|
|
RS485_Print_Error();
|
|
}
|
|
/* Set the Comm buffer size */
|
|
SetupComm(RS485_Handle, MAX_MPDU, MAX_MPDU);
|
|
/* raise DTR */
|
|
if (!EscapeCommFunction(RS485_Handle, SETDTR)) {
|
|
fprintf(stderr, "Unable to set DTR on %s\n", RS485_Port_Name);
|
|
RS485_Print_Error();
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Cleans up any handles that were created at startup.
|
|
* RETURN: none
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
static void RS485_Cleanup(
|
|
void)
|
|
{
|
|
if (!EscapeCommFunction(RS485_Handle, CLRDTR)) {
|
|
RS485_Print_Error();
|
|
}
|
|
|
|
if (!SetCommTimeouts(RS485_Handle, &RS485_Timeouts)) {
|
|
RS485_Print_Error();
|
|
}
|
|
|
|
CloseHandle(RS485_Handle);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Initializes the RS485 hardware and variables, and starts in
|
|
* receive mode.
|
|
* RETURN: none
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
void RS485_Initialize(
|
|
void)
|
|
{
|
|
RS485_Handle =
|
|
CreateFile(RS485_Port_Name, GENERIC_READ | GENERIC_WRITE, 0, 0,
|
|
OPEN_EXISTING,
|
|
/*FILE_FLAG_OVERLAPPED */ 0,
|
|
0);
|
|
if (RS485_Handle == INVALID_HANDLE_VALUE) {
|
|
fprintf(stderr, "Unable to open %s\n", RS485_Port_Name);
|
|
RS485_Print_Error();
|
|
exit(1);
|
|
}
|
|
if (!GetCommTimeouts(RS485_Handle, &RS485_Timeouts)) {
|
|
RS485_Print_Error();
|
|
}
|
|
RS485_Configure_Status();
|
|
#if PRINT_ENABLED
|
|
fprintf(stdout, "RS485 Interface: %s\n", RS485_Port_Name);
|
|
#endif
|
|
|
|
atexit(RS485_Cleanup);
|
|
|
|
return;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Returns the baud rate that we are currently running at
|
|
* RETURN: none
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
uint32_t RS485_Get_Baud_Rate(
|
|
void)
|
|
{
|
|
switch (RS485_Baud) {
|
|
case CBR_19200:
|
|
return 19200;
|
|
case CBR_38400:
|
|
return 38400;
|
|
case CBR_57600:
|
|
return 57600;
|
|
case CBR_115200:
|
|
return 115200;
|
|
case CBR_110:
|
|
return 110;
|
|
case CBR_300:
|
|
return 300;
|
|
case CBR_600:
|
|
return 600;
|
|
case CBR_1200:
|
|
return 1200;
|
|
case CBR_2400:
|
|
return 2400;
|
|
case CBR_4800:
|
|
return 4800;
|
|
case CBR_14400:
|
|
return 14400;
|
|
case CBR_56000:
|
|
return 56000;
|
|
case CBR_128000:
|
|
return 128000;
|
|
case CBR_256000:
|
|
return 256000;
|
|
case 76800:
|
|
/* See comments in RS485_Set_Baud_Rate() below
|
|
* also look at definition of CBR_xx in winbase.h
|
|
* some serial drivers will only support the defined
|
|
* baud rates but others will try and configure the
|
|
* requested baud rate (or as close as they can get)
|
|
*/
|
|
return 76800;
|
|
case CBR_9600:
|
|
default:
|
|
return 9600;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* DESCRIPTION: Sets the baud rate for the chip USART
|
|
* RETURN: none
|
|
* ALGORITHM: none
|
|
* NOTES: none
|
|
*****************************************************************************/
|
|
bool RS485_Set_Baud_Rate(
|
|
uint32_t baud)
|
|
{
|
|
bool valid = true;
|
|
|
|
switch (baud) {
|
|
case 9600:
|
|
RS485_Baud = CBR_9600;
|
|
break;
|
|
case 19200:
|
|
RS485_Baud = CBR_19200;
|
|
break;
|
|
case 38400:
|
|
RS485_Baud = CBR_38400;
|
|
break;
|
|
case 57600:
|
|
RS485_Baud = CBR_57600;
|
|
break;
|
|
case 115200:
|
|
RS485_Baud = CBR_115200;
|
|
break;
|
|
case 110:
|
|
RS485_Baud = CBR_110;
|
|
break;
|
|
case 300:
|
|
RS485_Baud = CBR_300;
|
|
break;
|
|
case 600:
|
|
RS485_Baud = CBR_600;
|
|
break;
|
|
case 1200:
|
|
RS485_Baud = CBR_1200;
|
|
break;
|
|
case 2400:
|
|
RS485_Baud = CBR_2400;
|
|
break;
|
|
case 4800:
|
|
RS485_Baud = CBR_4800;
|
|
break;
|
|
case 14400:
|
|
RS485_Baud = CBR_14400;
|
|
break;
|
|
case 56000:
|
|
RS485_Baud = CBR_56000;
|
|
break;
|
|
case 128000:
|
|
RS485_Baud = CBR_128000;
|
|
break;
|
|
case 256000:
|
|
RS485_Baud = CBR_256000;
|
|
break;
|
|
case 76800:
|
|
/* I'm using the B&B Electronics USOPTL4 USB RS485 adapter
|
|
* on Win 7 and building with VS2008 Express Edition and it
|
|
* seems to work for the most part if I use the following.
|
|
* I get the occasional data errors especially if the devices
|
|
* are transmitting with 1 stop bit (some devices receive with
|
|
* 1 stop bit but effectivly end up transmitting with 2 stop
|
|
* bits, usually because of synchroisation issues in some UARTs
|
|
* which mean that if you wait until the serialiser has finished
|
|
* with the current character and then load the TX buffer it has
|
|
* to wait until the next bit boundary to start transmitting.
|
|
* PMcS
|
|
*/
|
|
RS485_Baud = 76800;
|
|
break;
|
|
default:
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
if (valid) {
|
|
/* FIXME: store the baud rate */
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
/* Transmits a Frame on the wire */
|
|
void RS485_Send_Frame(
|
|
volatile struct mstp_port_struct_t *mstp_port, /* port specific data */
|
|
uint8_t * buffer, /* frame to send (up to 501 bytes of data) */
|
|
uint16_t nbytes)
|
|
{ /* number of bytes of data (up to 501) */
|
|
DWORD dwWritten = 0;
|
|
|
|
if (mstp_port) {
|
|
uint32_t baud;
|
|
uint8_t turnaround_time;
|
|
baud = RS485_Get_Baud_Rate();
|
|
/* wait about 40 bit times since reception */
|
|
if (baud == 9600)
|
|
turnaround_time = 4;
|
|
else if (baud == 19200)
|
|
turnaround_time = 2;
|
|
else
|
|
turnaround_time = 2;
|
|
while (mstp_port->SilenceTimer(NULL) < turnaround_time) {
|
|
/* do nothing - wait for timer to increment */
|
|
};
|
|
}
|
|
WriteFile(RS485_Handle, buffer, nbytes, &dwWritten, NULL);
|
|
|
|
/* per MSTP spec, reset SilenceTimer after each byte is sent */
|
|
if (mstp_port) {
|
|
mstp_port->SilenceTimerReset(NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* called by timer, interrupt(?) or other thread */
|
|
void RS485_Check_UART_Data(
|
|
volatile struct mstp_port_struct_t *mstp_port)
|
|
{
|
|
char lpBuf[1];
|
|
DWORD dwRead = 0;
|
|
|
|
if (mstp_port->ReceiveError == true) {
|
|
/* wait for state machine to clear this */
|
|
}
|
|
/* wait for state machine to read from the DataRegister */
|
|
else if (mstp_port->DataAvailable == false) {
|
|
/* check for data */
|
|
if (!ReadFile(RS485_Handle, lpBuf, sizeof(lpBuf), &dwRead, NULL)) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
mstp_port->ReceiveError = TRUE;
|
|
}
|
|
} else {
|
|
if (dwRead) {
|
|
mstp_port->DataRegister = lpBuf[0];
|
|
mstp_port->DataAvailable = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************************************************************
|
|
* Description: print available COM ports
|
|
* Returns: none
|
|
* Notes: none
|
|
**************************************************************************/
|
|
void RS485_Print_Ports(
|
|
void)
|
|
{
|
|
unsigned i = 0;
|
|
|
|
/* try to open all 255 COM ports */
|
|
for (i = 1; i < 256; i++) {
|
|
if (RS485_Interface_Valid(i)) {
|
|
/* note: format for Wireshark ExtCap */
|
|
printf("interface {value=COM%u}"
|
|
"{display=BACnet MS/TP on COM%u}\n", i, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef TEST_RS485
|
|
|
|
#include "mstpdef.h"
|
|
|
|
|
|
static void test_transmit_task(
|
|
void *pArg)
|
|
{
|
|
char *TxBuf = "BACnet MS/TP";
|
|
size_t len = strlen(TxBuf) + 1;
|
|
|
|
while (TRUE) {
|
|
Sleep(1000);
|
|
RS485_Send_Frame(NULL, &TxBuf[0], len);
|
|
}
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
static BOOL WINAPI CtrlCHandler(
|
|
DWORD dwCtrlType)
|
|
{
|
|
dwCtrlType = dwCtrlType;
|
|
exit(0);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static int ascii_hex_to_int(
|
|
char ch)
|
|
{
|
|
int rv = -1;
|
|
|
|
if ((ch >= '0') && (ch <= '9')) {
|
|
rv = ch - '0';
|
|
} else if ((ch >= 'a') && (ch <= 'f')) {
|
|
rv = 10 + ch - 'a';
|
|
} else if ((ch >= 'A') && (ch <= 'F')) {
|
|
rv = 10 + ch - 'a';
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
int main(
|
|
int argc,
|
|
char *argv[])
|
|
{
|
|
unsigned long hThread = 0;
|
|
uint32_t arg_value = 0;
|
|
char lpBuf[1];
|
|
DWORD dwRead = 0;
|
|
unsigned i = 0, len = 0, count = 0;
|
|
char hex_pair[5] = "0xff";
|
|
char ch = ' ';
|
|
int lsb = 0, msb = 0;
|
|
long my_baud = 38400;
|
|
uint8_t buffer[501] = { 0 };
|
|
|
|
if (argc > 1) {
|
|
RS485_Set_Interface(argv[1]);
|
|
}
|
|
if (argc > 2) {
|
|
my_baud = strtol(argv[2], NULL, 0);
|
|
}
|
|
RS485_Set_Baud_Rate(my_baud);
|
|
RS485_Initialize();
|
|
#if defined(_WIN32)
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
|
|
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlCHandler, TRUE);
|
|
#endif
|
|
#ifdef TEST_RS485_TRANSMIT
|
|
/* read a stream of characters from stdin or argument */
|
|
if (argc > 3) {
|
|
len = strlen(argv[3]);
|
|
for (i = 0; i < len; i++) {
|
|
/* grab pairs of hex characters, skip spaces */
|
|
ch = argv[3][i];
|
|
if (ch == ' ') {
|
|
continue;
|
|
}
|
|
msb = ascii_hex_to_int(ch);
|
|
if (msb >= 0) {
|
|
i++;
|
|
ch = argv[3][i];
|
|
lsb = ascii_hex_to_int(ch);
|
|
if (lsb >= 0) {
|
|
buffer[count] = msb << 4 | lsb;
|
|
} else {
|
|
buffer[count] = msb;
|
|
}
|
|
count++;
|
|
if (count >= sizeof(buffer)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
RS485_Send_Frame(NULL, buffer, count);
|
|
}
|
|
#endif
|
|
#ifdef TEST_RS485_RECEIVE
|
|
/* receive task */
|
|
for (;;) {
|
|
if (!ReadFile(RS485_Handle, lpBuf, sizeof(lpBuf), &dwRead, NULL)) {
|
|
if (GetLastError() != ERROR_IO_PENDING) {
|
|
RS485_Print_Error();
|
|
}
|
|
} else {
|
|
/* print any characters received */
|
|
if (dwRead) {
|
|
for (i = 0; i < dwRead; i++) {
|
|
fprintf(stderr, "%02X ", lpBuf[i]);
|
|
}
|
|
}
|
|
dwRead = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|