BACnet协议栈移植分析之一:rs485.c

从现在开始分析BACnet协议栈了,版本号是bacnet-stack-0.7.1。目录是bacnet-stack-0.7.1\ports\linux\rs485.c

rs485.c文件主要要解决在物理层发送和接收数据的作用。不同的开发板需要移植该文件。

#include <errno.h>

#include <stddef.h>

#include <stdint.h>

#include <stdbool.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

/* Linux includes */

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <termios.h>

#include <unistd.h>

#include <sched.h>

 

/* Local includes */

#include "mstp.h"

#include "rs485.h"

#include "fifo.h"

 

#include <sys/select.h>

#include <sys/time.h>

 

/* Posix serial programming reference:

http://www.easysw.com/~mike/serial/serial.html */

 

/* Use ionice wrapper to improve serial performance:

   $ sudo ionice -c 1 -n 0 ./bin/bacserv 12345

*/

 

/* handle returned from open() */

static int RS485_Handle = -1;

/* baudrate settings are defined in <asm/termbits.h>, which is

   included by <termios.h> */

static unsigned int RS485_Baud = B38400;//波特率选择38400 bps

/* serial port name, /dev/ttyS0,

  /dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */

static char *RS485_Port_Name = "/dev/ttyUSB0";      /*系统默认是通过USB转485的,根据需要设置,若你的开发板用485接口,则用static char *RS485_Port_Name = "/dev/ttyS0";代替 */

                                                   

/* some terminal I/O have RS-485 specific functionality */

#ifndef RS485MOD

#define RS485MOD 0

#endif

/* serial I/O settings */

static struct termios RS485_oldtio;

 

/* Ring buffer for incoming bytes, in order to speed up the receiving. */

static FIFO_BUFFER Rx_FIFO;

/* buffer size needs to be a power of 2 */

static uint8_t Rx_Buffer[4096];

 

#define _POSIX_SOURCE 1 /* POSIX compliant source */

 

/*********************************************************************

* DESCRIPTION: Configures the interface name

* RETURN:      none

* ALGORITHM:   none

* NOTES:       none

*********************************************************************/

void RS485_Set_Interface(

    char *ifname)

{

    /* note: expects a constant char, or char from the heap */

    if (ifname) {

        RS485_Port_Name = ifname;

    }

}

 

/*********************************************************************

* DESCRIPTION: Returns the interface name

* RETURN:      none

* ALGORITHM:   none

* NOTES:       none

*********************************************************************/

const char *RS485_Interface(

    void)

{

    return RS485_Port_Name;

}

 

/****************************************************************************

* DESCRIPTION: Returns the baud rate that we are currently running at

* RETURN:      none

* ALGORITHM:   none

* NOTES:       none

*****************************************************************************/

uint32_t RS485_Get_Baud_Rate(

    void)

{

    uint32_t baud = 0;

 

    switch (RS485_Baud) {

        case B0:

            baud = 0;

            break;

        case B50:

            baud = 50;

            break;

        case B75:

            baud = 75;

            break;

        case B110:

            baud = 110;

            break;

        case B134:

            baud = 134;

            break;

        case B150:

            baud = 150;

            break;

        case B200:

            baud = 200;

            break;

        case B300:

            baud = 300;

            break;

        case B600:

            baud = 600;

            break;

        case B1200:

            baud = 1200;

            break;

        case B1800:

            baud = 1800;

            break;

        case B2400:

            baud = 2400;

            break;

        case B4800:

            baud = 4800;

            break;

        case B9600:

            baud = 9600;

            break;

        case B19200:

            baud = 19200;

            break;

        case B38400:

            baud = 38400;

            break;

        case B57600:

            baud = 57600;

            break;

        case B115200:

            baud = 115200;

            break;

        case B230400:

            baud = 230400;

            break;

        default:

            baud = 9600;

    }

 

    return baud;

}

 

/****************************************************************************

* 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 0:

            RS485_Baud = B0;

            break;

        case 50:

            RS485_Baud = B50;

            break;

        case 75:

            RS485_Baud = B75;

            break;

        case 110:

            RS485_Baud = B110;

            break;

        case 134:

            RS485_Baud = B134;

            break;

        case 150:

            RS485_Baud = B150;

            break;

        case 200:

            RS485_Baud = B200;

            break;

        case 300:

            RS485_Baud = B300;

            break;

        case 600:

            RS485_Baud = B600;

            break;

        case 1200:

            RS485_Baud = B1200;

            break;

        case 1800:

            RS485_Baud = B1800;

            break;

        case 2400:

            RS485_Baud = B2400;

            break;

        case 4800:

            RS485_Baud = B4800;

            break;

        case 9600:

            RS485_Baud = B9600;

            break;

        case 19200:

            RS485_Baud = B19200;

            break;

        case 38400:

            RS485_Baud = B38400;

            break;

        case 57600:

            RS485_Baud = B57600;

            break;

        case 115200:

            RS485_Baud = B115200;

            break;

        case 230400:

            RS485_Baud = B230400;

            break;

        default:

            valid = false;

            break;

    }

 

    if (valid) {

        /* FIXME: store the baud rate */

    }

 

    return valid;

}

 

/****************************************************************************

* DESCRIPTION: Transmit a frame on the wire

* RETURN:      none

* ALGORITHM:   none

* NOTES:       none

*****************************************************************************/

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) */

    uint32_t turnaround_time = Tturnaround * 1000;

    uint32_t baud = RS485_Get_Baud_Rate();

    ssize_t written = 0;

    int greska;

 

    /* sleeping for turnaround time is necessary to give other devices

       time to change from sending to receiving state. */

    usleep(turnaround_time / baud);    //至少要等待Tturnaround时间才启动RS485缓冲器发送

    /*

       On  success,  the  number of bytes written are returned (zero indicates

       nothing was written).  On error, -1  is  returned,  and  errno  is  set

       appropriately.   If  count  is zero and the file descriptor refers to a

       regular file, 0 will be returned without causing any other effect.  For

       a special file, the results are not portable.

     */

    written = write(RS485_Handle, buffer, nbytes);   //RS485_Handle初始时是为-1,当发出open操作时应该是个正整数,而write函数的原型是(fd,buf,n)

    greska = errno;

    if (written <= 0) {

        printf("write error: %s\n", strerror(greska));

    } else {

        /* wait until all output has been transmitted. */

        tcdrain(RS485_Handle); //Linux中的传输等待函数

    }

    /*  tcdrain(RS485_Handle); */

    /* per MSTP spec, sort of */

    if (mstp_port) {

        mstp_port->SilenceTimerReset();

    }

 

    return;

}

 

/****************************************************************************

* DESCRIPTION: Get a byte of receive data

* RETURN:      none

* ALGORITHM:   none

* NOTES:       none

*****************************************************************************/

RS485_Check_UART_Data函数分别检查ReceiveErrorDataAvailable。然后选择端口从UART中读数据

void RS485_Check_UART_Data(

    volatile struct mstp_port_struct_t *mstp_port)

{

    fd_set input;

    struct timeval waiter;

    uint8_t buf[2048];

    int n;

 

    if (mstp_port->ReceiveError == true) {

        /* do nothing but wait for state machine to clear the error */

        /* burning time, so wait a longer time */

        waiter.tv_sec = 0;

        waiter.tv_usec = 5000;

    } else if (mstp_port->DataAvailable == false) {

        /* wait for state machine to read from the DataRegister */

        if (FIFO_Count(&Rx_FIFO) > 0) {

            /* data is available */

            mstp_port->DataRegister = FIFO_Get(&Rx_FIFO);

            mstp_port->DataAvailable = true;

            /* FIFO is giving data - don't wait very long */

            waiter.tv_sec = 0;

            waiter.tv_usec = 10;

        } else {

            /* FIFO is empty - wait a longer time */

            waiter.tv_sec = 0;

            waiter.tv_usec = 5000;

        }

    }

    /* grab bytes and stuff them into the FIFO every time */

    FD_ZERO(&input);

    FD_SET(RS485_Handle, &input);

    n = select(RS485_Handle + 1, &input, NULL, NULL, &waiter);

    if (n < 0) {

        return;

    }

    if (FD_ISSET(RS485_Handle, &input)) {

        n = read(RS485_Handle, buf, sizeof(buf));

        FIFO_Add(&Rx_FIFO, &buf[0], n);

    }

}

 

void RS485_Cleanup(

    void)

{

    /* restore the old port settings */

    tcsetattr(RS485_Handle, TCSANOW, &RS485_oldtio); //tcgetattr用于获取终端的相关参数,而tcsetattr函数用于设置终端参数

    close(RS485_Handle);

}

 

 

void RS485_Initialize(

    void)

{

    struct termios newtio;

    printf("RS485: Initializing %s", RS485_Port_Name);

    /*

       Open device for reading and writing.

       Blocking mode - more CPU effecient

     */

    RS485_Handle = open(RS485_Port_Name, O_RDWR | O_NOCTTY /*| O_NDELAY */ );

    if (RS485_Handle < 0) {

        perror(RS485_Port_Name);

        exit(-1);

    }

#if 0

    /* non blocking for the read */

    fcntl(RS485_Handle, F_SETFL, FNDELAY);

#else

    /* efficient blocking for the read */

    fcntl(RS485_Handle, F_SETFL, 0);

#endif

    /* save current serial port settings */

    tcgetattr(RS485_Handle, &RS485_oldtio);

    /* clear struct for new port settings */

    bzero(&newtio, sizeof(newtio));

    /*

       BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.

       CRTSCTS : output hardware flow control (only used if the cable has

       all necessary lines. See sect. 7 of Serial-HOWTO)

       CS8     : 8n1 (8bit,no parity,1 stopbit)

       CLOCAL  : local connection, no modem contol

       CREAD   : enable receiving characters

     */

    newtio.c_cflag = RS485_Baud | CS8 | CLOCAL | CREAD | RS485MOD;

    /* Raw input */

    newtio.c_iflag = 0;

    /* Raw output */

    newtio.c_oflag = 0;

    /* no processing */

    newtio.c_lflag = 0;

    /* activate the settings for the port after flushing I/O */

    tcsetattr(RS485_Handle, TCSAFLUSH, &newtio);

    /* destructor */

    atexit(RS485_Cleanup);

    /* flush any data waiting */

    usleep(200000);

    tcflush(RS485_Handle, TCIOFLUSH);

    /* ringbuffer */

    FIFO_Init(&Rx_FIFO, Rx_Buffer, sizeof(Rx_Buffer));

    printf("=success!\n");

}

/*以上都是为了兼容POSIX接口而编写的,Linux系统中最关键的是一下代码*/

#ifdef TEST_RS485

#include <string.h>

int main(

    int argc,

    char *argv[])

{

    uint8_t buf[8];

    char *wbuf = { "BACnet!" };

    size_t wlen = strlen(wbuf) + 1;

    unsigned i = 0;

    size_t written = 0;

    int rlen;

 

/*设置串口,如波特率,端口,初始化*/

    /* argv has the "/dev/ttyS0" or some other device */

    if (argc > 1) {

        RS485_Set_Interface(argv[1]);

    }

    RS485_Set_Baud_Rate(38400);

    RS485_Initialize();

 

/*循环读取串口的数据*/

    for (;;) {

        written = write(RS485_Handle, wbuf, wlen);

        rlen = read(RS485_Handle, buf, sizeof(buf));

        /* print any characters received */

        if (rlen > 0) {

            for (i = 0; i < rlen; i++) {

                fprintf(stderr, "%02X ", buf[i]); //长度不足2的话前面补0

            }

        }

    }

 

    return 0;

}

#endif

 

补充:

1、select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型: 
         #include <sys/time.h> 
         #include <unistd.h> 
         int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout); 
     参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。 
     fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作: 
      FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。 
      FD_SET(int fd, fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。 
      FD_CLR(int fd, fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。 
      FD_ISSET(int fd, fd_set *fdset);用于测试指定的文件描述符是否在该集合中。         
     过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏: 
     fd_set set;
     FD_ZERO(&set);      
     FD_SET(0, &set);    
     FD_CLR(4, &set);      
     FD_ISSET(5, &set);    
―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必须<FD_SETSIZE。

 2、struct termios结构体  

一、数据成员

termios 函数族提供了一个常规的终端接口,用于控制非同步通信端口。 这个结构包含了至少下列成员: 
tcflag_t c_iflag;      /* 输入模式 */
tcflag_t c_oflag;      /* 输出模式 */
tcflag_t c_cflag;      /* 控制模式 */
tcflag_t c_lflag;      /* 本地模式 */
cc_t c_cc[NCCS];       /* 控制字符 */
struct termios
{unsigned short c_iflag;  /* 输入模式标志*/
unsigned short c_oflag;  /* 输出模式标志*/
unsigned short c_cflag;  /* 控制模式标志*/
unsigned short c_lflag;   /*区域模式标志或本地模式标志或局部模式*/
unsigned char c_line;  /*行控制line discipline */
unsigned char c_cc[NCC];   /* 控制字符特性*/
};

 

你可能感兴趣的:(BACnet协议栈移植分析之一:rs485.c)