最近一段时间在搞Linux 跟stm32单片机的485通信,Linux系统所在的板上将发送及接收数据引脚接到485芯片上,从而完成RS232到RS485信号的转换。
此篇博客暂时选择了前者--让接收方延时30ms回复!下面主要记录的是Linux串口编程对485编程应用层代码的优化。
RS232是全双工的通信协议,而485是单双工的,因此借用Linux串口驱动进行数据收发,就要手动控制485芯片的数据方向引脚。在Linux的串口驱动中默认是不支持485的,并且也没有支持485的串口驱动,除非自己改串口驱动实现!
一般来说,控制485芯片的数据方向,应用层iotcl控制就行,即在用户发送数据之前将485芯片控为发送状态,发送完数据马上更改为接收状态,对此对应用层的串口用户信息结构中,我添加了两个方向控制函数,用户只需要在初始化串口的时候将其赋值即可。
/*存放串口信息结构*/
typedef struct __UartDevice
{
int fd; /*串口设备fd*/
int baudrate; /*串口波特率*/
int flow_ctl; /*硬件流控制标记*/
int stopbit; /*停止位*/
int databit; /*数据位*/
char parity; /*极性*/
int nRcvTimeOutSec; /*串口接收等待超时*/
char chOpenDevName[128]; /*串口设备名称*/
void *pUerArg; /*用户参数*/
fUartCallback pfunc485SetDirRead; /*485方向设置读状态函数*/
fUartCallback pfunc485SetDirWrite; /*485方向设置发送状态函数*/
/***********************************************************/
int nTimeOutFlag; /*时间到*/
pthread_t pthreadDelayID; /*线程id*/
pthread_cond_t cDelayCond; /*条件变量*/
pthread_mutex_t mDelayMutex; /*访问锁*/
pthread_cond_t cWaitStartCond; /*条件变量*/
pthread_mutex_t mWaitStartMutex; /*访问锁*/
/***********************************************************/
}UartDevice;
在实际的串口编程中,一般会设置串口接收延时,如果串口在一定时间内没有收到数据就会返回,串口设置延时一般是使用select函数对串口文件描述符进行监控,这个一般没有什么问题。
因此,我在Linux串口模块添加了一个线程专门处理这种读取到销量数据而快速返回问题。
uart.h文件:
#ifndef __USART_H__
#define __USART_H__
typedef int (*fUartCallback)(void *ptr);
/*存放串口信息结构*/
typedef struct __UartDevice
{
int fd; /*串口设备fd*/
int baudrate; /*串口波特率*/
int flow_ctl; /*硬件流控制标记*/
int stopbit; /*停止位*/
int databit; /*数据位*/
char parity; /*极性*/
int nRcvTimeOutSec; /*串口接收等待超时*/
char chOpenDevName[128]; /*串口设备名称*/
void *pUerArg; /*用户参数*/
fUartCallback pfunc485SetDirRead; /*485方向设置函数*/
fUartCallback pfunc485SetDirWrite; /*485方向设置函数*/
/***********************************************************/
int nTimeOutFlag; /*时间到*/
pthread_t pthreadDelayID; /*线程id*/
pthread_cond_t cDelayCond; /*条件变量*/
pthread_mutex_t mDelayMutex; /*访问锁*/
pthread_cond_t cWaitStartCond; /*条件变量*/
pthread_mutex_t mWaitStartMutex; /*访问锁*/
/***********************************************************/
}UartDevice;
int UART_Open(UartDevice *pstUartDeviceInfo);
void UART_Close(UartDevice *pstUartDeviceInfo);
int UART_Set(UartDevice *pstUartDeviceInfo);
int UART_Init(UartDevice *pstUartDeviceInfo);
int UART_Send(UartDevice *pstUartDeviceInfo,const void *send_buf,int data_len);
int UART_Recv(UartDevice *pstUartDeviceInfo,void *rcv_buf,int data_len);
int UART_DataSend(UartDevice *pstUartDeviceInfo,const void *send_buf,int data_len);
int UART_DataRecv(UartDevice *pstUartDeviceInfo,void *rcv_buf,int data_len);
void UART_FlushBuffer(UartDevice *pstUartDeviceInfo);
#endif
uart.c文件:
#include /*标准输入输出定义*/
#include /*标准函数库定义*/
#include /*Unix 标准函数定义*/
#include
#include
#include /*文件控制定义*/
#include /*PPSIX 终端控制定义*/
#include /*错误号定义*/
#include
#include
#include "usart.h"
/*******************************************************************
* 名称: UART0_Open
* 功能: 打开串口并返回串口设备文件描述
* 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_Open(UartDevice *pstUartDeviceInfo)
{
if(!pstUartDeviceInfo)
return -1;
//打开串口
pstUartDeviceInfo->fd = open(pstUartDeviceInfo->chOpenDevName, O_RDWR|O_NOCTTY|O_NDELAY);
if(pstUartDeviceInfo->fd<0){
perror("Open Serial Port Failed!\n");
return(-1);
}
//恢复串口为阻塞状态
if(fcntl(pstUartDeviceInfo->fd, F_SETFL, 0) < 0){
printf("fcntl failed!\n");
goto iExit;
}
else
printf("fcntl=%d\n",fcntl(pstUartDeviceInfo->fd, F_SETFL,0));
//测试是否为终端设备,是1,否0
if(0 == isatty(pstUartDeviceInfo->fd))
{
printf("%s Serial is not a terminal device\n",pstUartDeviceInfo->chOpenDevName);
goto iExit;
}
else
printf("%s is terminal device!\n",pstUartDeviceInfo->chOpenDevName);
return 0;
iExit:
UART_Close(pstUartDeviceInfo); /*串口关闭*/
return -1;
}
/*******************************************************************
* 名称: UART0_Close
* 功能: 关闭串口并返回串口设备文件描述
* 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
* 出口参数: void
*******************************************************************/
void UART_Close(UartDevice *pstUartDeviceInfo)
{
close(pstUartDeviceInfo->fd);
}
/*******************************************************************
* 名称: UART0_Set
* 功能: 设置串口数据位,停止位和效验位
* 入口参数: fd 串口文件描述符
* speed 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_Set(UartDevice *pstUartDeviceInfo)
{
int i;
int status;
int speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300};
struct termios options;
/*tcgetattr(fd,&options)得到与fd指向对象的相关参数
并将它们保存于options,该函数还可以测试配置是否正确,
该串口是否可用等。若调用成功,函数返回值为0,
若调用失败,函数返回值为1.
*/
if (tcgetattr( pstUartDeviceInfo->fd,&options) != 0)
{
perror("tcgetattr failed!\n");
return(-1);
}
//设置串口输入波特率和输出波特率
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
if (pstUartDeviceInfo->baudrate == name_arr[i])
{
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
//修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;
//修改控制模式,使得能够从串口中读取输入数据
options.c_cflag |= CREAD;
//设置数据流控制
switch(pstUartDeviceInfo->flow_ctl)
{
case 0 ://不使用流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1 ://使用硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2 ://使用软件流控制
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
//设置数据位
//屏蔽其他标志位
options.c_cflag &= ~CSIZE;
switch (pstUartDeviceInfo->databit)
{
case 5 :
options.c_cflag |= CS5;
break;
case 6 :
options.c_cflag |= CS6;
break;
case 7 :
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n");
return (-1);
}
//设置校验位
switch (pstUartDeviceInfo->parity)
{
case 'n':
case 'N': //无奇偶校验位。
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O'://设置为奇校验
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E'://设置为偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S': //设置为空格
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parity\n");
return (-1);
}
// 设置停止位
switch (pstUartDeviceInfo->stopbit)
{
case 1:
options.c_cflag &= ~CSTOPB; break;
case 2:
options.c_cflag |= CSTOPB; break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (-1);
}
//修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//我加的
//options.c_lflag &= ~(ISIG | ICANON);
//设置等待时间和最小接收字符
options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
options.c_cc[VMIN] = 0; /* 读取字符的最少个数为1 */
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush(pstUartDeviceInfo->fd,TCIFLUSH);
//激活配置 (将修改后的termios数据设置到串口中)
if (tcsetattr(pstUartDeviceInfo->fd,TCSANOW,&options) != 0)
{
perror("com set error!\n");
return (-1);
}
return (0);
}
/********************************************************************
* 名称: UART0_Send
* 功能: 发送数据
* 入口参数:
*fd :文件描述符
* send_buf :存放串口发送数据
* data_len :一帧数据的个数
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_Send(UartDevice *pstUartDeviceInfo,const void *send_buf,int data_len)
{
int len = 0;
if(pstUartDeviceInfo->pfunc485SetDirWrite!=NULL)
pstUartDeviceInfo->pfunc485SetDirWrite(pstUartDeviceInfo->pUerArg);
usleep(500);//先拉高电平一段时间
len = write(pstUartDeviceInfo->fd,send_buf,data_len);
if(pstUartDeviceInfo->pfunc485SetDirRead!=NULL)
{
tcdrain(pstUartDeviceInfo->fd);//等待数据发送完成
pstUartDeviceInfo->pfunc485SetDirRead(pstUartDeviceInfo->pUerArg);
}
return len;
}
/*******************************************************************
* 名称: UART0_Recv
* 功能: 接收串口数据
* 入口参数:
*fd :文件描述符
*rcv_buf :接收串口中数据存入rcv_buf缓冲区中
*data_len :一帧数据的长度
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_Recv(UartDevice *pstUartDeviceInfo,void *rcv_buf,int data_len)
{
int len,fd_ret;
fd_set fds;
struct timeval time;
FD_ZERO(&fds);
FD_SET(pstUartDeviceInfo->fd,&fds);
time.tv_sec = 0;
time.tv_usec = (1000*500);
//使用select实现串口的多路通信
fd_ret = select(pstUartDeviceInfo->fd+1,&fds,NULL,NULL,&time);
if(fd_ret)
{
len = read(pstUartDeviceInfo->fd,rcv_buf,data_len);
return len;
}
else
{
return -1;
}
}
/*******************************************************************
* 名称: UART0_Init()
* 功能: 串口初始化
* 入口参数: fd : 文件描述符
* speed : 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
void UART_SignalStartCond(UartDevice *pstUartDeviceInfo)
{
pstUartDeviceInfo->nTimeOutFlag = 0;
pthread_mutex_lock(&pstUartDeviceInfo->mWaitStartMutex);
pthread_cond_signal(&pstUartDeviceInfo->cWaitStartCond);
pthread_mutex_unlock(&pstUartDeviceInfo->mWaitStartMutex);
}
/*******************************************************************
* 名称: UART0_Init()
* 功能: 串口初始化
* 入口参数: fd : 文件描述符
* speed : 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
void UART_SignalDelayCond(UartDevice *pstUartDeviceInfo)
{
pthread_mutex_lock(&pstUartDeviceInfo->mDelayMutex);
pthread_cond_signal(&pstUartDeviceInfo->cDelayCond);
pthread_mutex_unlock(&pstUartDeviceInfo->mDelayMutex);
}
/********************************************************************
* 名称: UART0_Send
* 功能: 发送数据
* 入口参数:
*fd :文件描述符
* send_buf :存放串口发送数据
* data_len :一帧数据的个数
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_DataSend(UartDevice *pstUartDeviceInfo,const void *send_buf,int data_len)
{
int len = 0;
len = UART_Send(pstUartDeviceInfo,send_buf, data_len);
return len;
}
/*******************************************************************
* 名称: UART0_Recv
* 功能: 接收串口数据
* 入口参数:
*fd :文件描述符
*rcv_buf :接收串口中数据存入rcv_buf缓冲区中
*data_len :一帧数据的长度
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_DataRecv(UartDevice *pstUartDeviceInfo,void *rcv_buf,int data_len)
{
int nReadLen1 = 0;
int nRcvSize = 0;
int nCntTime = 0;
int nStartFlag = 0;
pstUartDeviceInfo->nTimeOutFlag = 0;
while(nRcvSize0){
nRcvSize +=nReadLen1;
}
else
{
if(nStartFlag==0){
nStartFlag=1;
UART_SignalStartCond(pstUartDeviceInfo);
}
if(pstUartDeviceInfo->nTimeOutFlag==1)
break;
}
}
if(nStartFlag==1 && pstUartDeviceInfo->nTimeOutFlag==0)
UART_SignalDelayCond(pstUartDeviceInfo);
return nRcvSize;
}
/*******************************************************************
* 名称: UART_FlushBuffer
* 功能: 接收串口数据
* 入口参数:
*fd :文件描述符
*rcv_buf :接收串口中数据存入rcv_buf缓冲区中
*data_len :一帧数据的长度
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
void UART_FlushBuffer(UartDevice *pstUartDeviceInfo)
{
tcflush(pstUartDeviceInfo->fd, TCIFLUSH);
}
/*******************************************************************
* 名称: UART0_Init()
* 功能: 串口初始化
* 入口参数: fd : 文件描述符
* speed : 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
void *UART_DelayThread(void* arg)
{
int nRet = -1;
struct timespec tmspec;
UartDevice *pstUartDeviceInfo = (UartDevice *)arg;
while(1)
{
clock_gettime(CLOCK_MONOTONIC, &tmspec);
tmspec.tv_sec += 1;
pthread_mutex_lock(&pstUartDeviceInfo->mDelayMutex);
nRet = pthread_cond_timedwait(&pstUartDeviceInfo->cDelayCond, &pstUartDeviceInfo->mDelayMutex, &tmspec);
pthread_mutex_unlock(&pstUartDeviceInfo->mDelayMutex);
if(nRet!=0){
pstUartDeviceInfo->nTimeOutFlag = 1;
//printf("nRet=%d--->time out=%lu\n",nRet,tmspec.tv_sec);
}
/*线程阻塞*/
pthread_mutex_lock(&pstUartDeviceInfo->mWaitStartMutex);
pthread_cond_wait(&pstUartDeviceInfo->cWaitStartCond, &pstUartDeviceInfo->mWaitStartMutex);
pthread_mutex_unlock(&pstUartDeviceInfo->mWaitStartMutex);
}
}
/*******************************************************************
* 名称: UART0_Init()
* 功能: 串口初始化
* 入口参数: fd : 文件描述符
* speed : 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART_Init(UartDevice *pstUartDeviceInfo)
{
int nRet = -1;
pthread_condattr_t condattr;
/*打开串口*/
nRet = UART_Open(pstUartDeviceInfo);
if(nRet<0){
printf("UART_Open failed !\n");
return -1;
}
//设置串口数据帧格式
nRet = UART_Set(pstUartDeviceInfo);
if(nRet<0){
UART_Close(pstUartDeviceInfo);
return -1;
}
pthread_condattr_init(&condattr);
pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
pthread_cond_init(&pstUartDeviceInfo->cDelayCond, &condattr);
pthread_condattr_destroy(&condattr);
pthread_mutex_init(&pstUartDeviceInfo->mDelayMutex, NULL);
pthread_mutex_init(&pstUartDeviceInfo->mWaitStartMutex, NULL);
pthread_cond_init (&pstUartDeviceInfo->cWaitStartCond, NULL);
if(pthread_create(&pstUartDeviceInfo->pthreadDelayID, NULL, UART_DelayThread, pstUartDeviceInfo)!=0)
{
printf("%s func pthread_create error! at (%d) lines\n",__FUNCTION__,__LINE__);
return -1;
}
return 0;
}
测试程序main.c文件:
int main()
{
int nRet = 0;
char buf[1024]={0};
int size = 0;
char chcmd[]={0x8f,0x1,0x3,0x0,0x0};
UartDevice stUartDeviceInfo;
nRet = HwInit();
if(nRet <0){
printf("HwInit failed\n");
return -1;
}
memset(&stUartDeviceInfo,0,sizeof(stUartDeviceInfo));
memcpy(stUartDeviceInfo.chOpenDevName,TTYDEV_NAME,strlen(TTYDEV_NAME));
stUartDeviceInfo.fd = -1;
stUartDeviceInfo.baudrate = 9600;
stUartDeviceInfo.flow_ctl = 0;
stUartDeviceInfo.stopbit = 1;
stUartDeviceInfo.databit = 8;
stUartDeviceInfo.parity = 'N';
stUartDeviceInfo.pUerArg = NULL;
stUartDeviceInfo.nRcvTimeOutSec = 2;
stUartDeviceInfo.pfunc485SetDirRead = ioSet485Read;
stUartDeviceInfo.pfunc485SetDirWrite = ioSet485Write;
nRet = UART_Init(&stUartDeviceInfo);
if(nRet <0){
printf("UART_Init failed\n");
return -1;
}
while(1)
{
memset(buf,0,sizeof(buf));
nRet = UART_DataSend(&stUartDeviceInfo,chcmd,sizeof(chcmd)/sizeof(char));
printHex(chcmd,nRet,"snd:");
nRet = UART_DataRecv(&stUartDeviceInfo,buf,5);
printHex(buf,nRet,"rcv:");
usleep(300*1000);
}
UART_Close(&stUartDeviceInfo);
return 0;
}