一、前言
上一篇博文记录了使用wiringPi提供的串口驱动wiringSerial.c wiringSerial.h,并基于该驱动对串口进行简单的通信,测试中发现该串口的驱动比较简单,接收数据会存在分包的现象,另外一点是串口配置只提供了波特率参数配置,未提供其他如校验、数据位和停止位驱动。这一片博文主要是对驱动进行修改。
二、修改驱动
wiringPi的驱动源码可以在https://projects.drogon.net/raspberry-pi/wiringpi/中下载。找到wiringSerial.c 和 wiringSerial.h两个文件,复制一份并重名命为wiringSerial_Driver.c wiringSerial_Driver.h。以便于我们修改。
具体地,修改包括初始化函数、串口接收函数和串口发送函数。
2.1 初始化函数修改
可以同时初始化波特率、停止位、校验位和数据位。参数gsfd为全局定义的串口打开设备ID号,static int gsfd;
linux串口编程主要是对termios进行初始化,需要包含头文件:#include <termios.h>
在Rpi3B中输入:
man termios
可以查看termios的描述,具体不多讲,看说明文档即可,以下仅罗列部分:
NAME
termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed, cfsetspeed - get
and set terminal attributes, line control, get and set baud rate
SYNOPSIS
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions,
const struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios *termios_p, speed_t speed);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
cfsetspeed(), cfmakeraw(): _BSD_SOURCE
DESCRIPTION
The termios functions describe a general terminal interface that is provided to control asynchronous communications ports.
The termios structure
Many of the functions described here have a termios_p argument that is a pointer to a termios structure. This structure contains at least the follow‐
ing members:
tcflag_t c_iflag; /* input modes */
tcflag_t c_oflag; /* output modes */
tcflag_t c_cflag; /* control modes */
tcflag_t c_lflag; /* local modes */
cc_t c_cc[NCCS]; /* special characters */
The values that may be assigned to these fields are described below. In the case of the first four bit-mask fields, the definitions of some of the
associated flags that may be set are exposed only if a specific feature test macro (see feature_test_macros(7)) is defined, as noted in brackets
("[]").
In the descriptions below, "not in POSIX" means that the value is not specified in POSIX.1-2001, and "XSI" means that the value is specified in
POSIX.1-2001 as part of the XSI extension.
那么具体的驱动可以改写成一下内容:
int myserialOpen (const char *device, const int baud, const int nbit, const char parity, const int nstop)
{
struct termios options ;
speed_t myBaud ;
int status;
switch (baud)
{
case 50: myBaud = B50 ; break ;
case 75: myBaud = B75 ; break ;
case 110: myBaud = B110 ; break ;
case 134: myBaud = B134 ; break ;
case 150: myBaud = B150 ; break ;
case 200: myBaud = B200 ; break ;
case 300: myBaud = B300 ; break ;
case 600: myBaud = B600 ; break ;
case 1200: myBaud = B1200 ; break ;
case 1800: myBaud = B1800 ; break ;
case 2400: myBaud = B2400 ; break ;
case 4800: myBaud = B4800 ; break ;
case 9600: myBaud = B9600 ; break ;
case 19200: myBaud = B19200 ; break ;
case 38400: myBaud = B38400 ; break ;
case 57600: myBaud = B57600 ; break ;
case 115200: myBaud = B115200 ; break ;
case 230400: myBaud = B230400 ; break ;
case 460800: myBaud = B460800 ; break ;
case 500000: myBaud = B500000 ; break ;
case 576000: myBaud = B576000 ; break ;
case 921600: myBaud = B921600 ; break ;
case 1000000: myBaud = B1000000 ; break ;
case 1152000: myBaud = B1152000 ; break ;
case 1500000: myBaud = B1500000 ; break ;
case 2000000: myBaud = B2000000 ; break ;
case 2500000: myBaud = B2500000 ; break ;
case 3000000: myBaud = B3000000 ; break ;
case 3500000: myBaud = B3500000 ; break ;
case 4000000: myBaud = B4000000 ; break ;
default:
return -2 ;
}
/* 有O_NONBLOCK 即为非阻塞方式,默认阻塞方式,等待vim vtime*/
if ((gsfd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)
return -1 ;
fcntl (gsfd, F_SETFL, O_RDWR) ;
// Get and modify current options:
tcgetattr (gsfd, &options) ;
cfmakeraw (&options) ;
cfsetispeed (&options, myBaud) ;
cfsetospeed (&options, myBaud) ;
//data bit
switch (nbit)
{
case 7: options.c_cflag &= ~CSIZE ; options.c_cflag |= CS7; break;
case 8: options.c_cflag &= ~CSIZE ; options.c_cflag |= CS8; break;
default: options.c_cflag &= ~CSIZE ; options.c_cflag |= CS8; break;
}
//data parity
switch(parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB ;
options.c_cflag &= ~INPCK;
break;
case 'o':
case 'O':
options.c_cflag |= PARENB ;
options.c_cflag |= PARODD ;
options.c_cflag |= INPCK ;
options.c_cflag |= ISTRIP ;
break;
case 'e':
case 'E':
options.c_cflag |= PARENB ;
options.c_cflag &= ~PARODD;
options.c_cflag |= INPCK ;
options.c_cflag |= ISTRIP ;
break;
default:
options.c_cflag &= ~PARENB ;
options.c_cflag &= ~INPCK;
break;
}
//data stopbits
switch(nstop)
{
case 1: options.c_cflag &= ~CSTOPB ; break;
case 2: options.c_cflag |= CSTOPB ; break;
default: options.c_cflag &= ~CSTOPB ; break;
}
options.c_cflag |= (CLOCAL | CREAD) ;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;
options.c_oflag &= ~OPOST ;
options.c_cc [VMIN] = 0 ;
options.c_cc [VTIME] = 10 ; // 0.5 seconds (100 deciseconds)
tcsetattr (gsfd, TCSANOW, &options) ;
ioctl (gsfd, TIOCMGET, &status);
status |= TIOCM_DTR ;
status |= TIOCM_RTS ;
ioctl (gsfd, TIOCMSET, &status);
usleep (10000) ; // 10mS
return gsfd ;
}
2.2 接收函数修改
提一下,Linux串口接收主要由阻塞和非阻塞接收两种方式,关于如何配置Linux串口驱动可以参考这篇博文:Linux 使用fcntl c_cc[VMIN] c_cc[CTIME]设置串口阻塞与非阻塞读取数据
实际测试时发现wiringSerial的接收函数serialDataAvail缓存大小只有8字符,因此,超过8字符的数据将会分包,接收函数修改主要是对数据进行拼包,具体实现如下:
int myserialDataRead( char *buf, int * size)
{
int size_i,i;
i = 0;
size_i = 0;
while(1)
{
i = read(gsfd, buf+size_i ,1024);
size_i += i;
if(i == 8)
{
}
else if(i>0 && i <= 8)
{
*size = size_i;
return 0;
}
else
{
return -1;
}
}
}
2.3 发送函数修改
发送函数基本和wiringSerial提供的驱动一致,只是将传入的设备ID用全局变量替代,这样在使用该函数时无需输入串口的ID。具体修改如下:
void myserialDataSend(const char * ptr,int size)
{
while(size--)
{
serialPutchar(gsfd,*(ptr++));
}
}
三、编译测试
对应的头文件增加函数的声明,编写main.c测试文件:
#include<wiringPi.h>
#include"wiringSerial_Driver.h"
#include<stdio.h>
#include<string.h>
int main()
{
int filedevid;
int recbytenum;
int rxflag;
int i;
char buf[1024];
char bufprintf[1024];
memset(buf,0,1024);
memset(bufprintf,0,1024);
wiringPiSetup();
if((filedevid=myserialOpen("/dev/ttyAMA0",115200,8,'E',1))<0)
{
printf("/dev/ttyAMA0 Open Faild\r\n");
return -1;
}
else
{
printf("/dev/ttyAMA0 Open with 115200,115200,8,'E',1, success\r\n");
while(1)
{
rxflag = myserialDataRead(buf,&recbytenum);
if(rxflag != -1)
{
printf("Rpi3 uart rx %d byte: %s\r\n",recbytenum,buf);
sprintf(bufprintf,"Rpi3 uart Tx %d byte: %s\r\n",recbytenum,buf);
myserialDataSend(bufprintf, recbytenum);
}
}
}
}
编写makefile,进行编译并运行如下:
可以看到,数据以及拼包成功,说明驱动修改有效。