折腾这个折腾了两天,功夫不负有心人,终于还是折腾出来了。关于串口的实现,涉及的比较多,不过也没有想象中的那么难。之所以折腾那么就,主要还是犯了一个很低级的出错,就是在读数据的时候老出错(写数据老早就可以了,幸庆自己还有那么一点完美主义)。一开是使用的是网上广泛流传的那个文件包叫“qextserialport-1.2win-alpha”,用到了里面的几个文件。 posix_qextserialport.h qextserialbase.h qetserialport.h 以及其对应的.cpp文件。这些文件里面已经写好了对串口的初始化,打开,关闭,读写等等。可是我觉得内容太多,很多功能和我要实现的简单收发功能不相关。先给出我的实现函数
mainwindow.h文件,该文件主要定义一些Textbrowser之类的类和引用头文件“posix_qextserialport.h”,以及一些初始化串口,打开串口的槽函数。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include <stdio.h>
#include <termios.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "posix_qextserialport.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
public slots:
void init_serial();
void read_serial();
void open_serial();
void close_serial();
void send_serial();
public:
Posix_QextSerialPort *myCom;
QSocketNotifier *notifier;
};
#endif // MAINWINDOW_H
mainwindow.cpp文件,该文件具体实现各个函数的功能以及信号与槽之间的关系。
#include "mainwindow.h"
#include "ui_mainwindow.h"
//static int serial_fd;
struct PortSettings myComSetting={BAUD115200,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
init_serial();//初始化串口
//开始时三个按钮的状态
ui->setupUi(this);
ui->closeButton->setEnabled(false);
ui->sendButton->setEnabled(false);
//各个信号与槽之间的关系
//notifier=new QSocketNotifier()
connect(myCom,SIGNAL(readyRead()),this,SLOT(read_serial()));//
connect(ui->openButton,SIGNAL(clicked()),this,SLOT(open_serial()));
connect(ui->closeButton,SIGNAL(clicked()),this,SLOT(close_serial()));
connect(ui->sendButton,SIGNAL(clicked()),this,SLOT(send_serial()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::init_serial()
{
myCom=new Posix_QextSerialPort("/dev/ttyUSB1",myComSetting,QextSerialBase::EventDriven);
//如果在电脑端则选择ttyUSB0,如果在开发板端则选择ttySAC0
}
void MainWindow::read_serial()
{
QByteArray temp=myCom->readAll();//全部读取
ui->textEdit->insertPlainText(temp);//显示读取到的数据
}
void MainWindow::open_serial()
{
myCom->open(QIODevice::ReadWrite);//打开串口
ui->openButton->setEnabled(false);
ui->closeButton->setEnabled(true);
ui->sendButton->setEnabled(true);
}
void MainWindow::close_serial()
{
myCom->close();//关闭串口
ui->openButton->setEnabled(true);
ui->sendButton->setEnabled(false);
ui->closeButton->setEnabled(false);
}
void MainWindow::send_serial()
{
myCom->write(ui->sendlineEdit->text().toAscii());//转化为ASCII码
}
至此总结:该工程可以实现,串口的发送,可是接收老出现问题,我还尝试这像前面一样加入QSocketNotifier,在PC端可以接收,可是
很容易出现乱码,可能是跟终端共有一个串口的原因,于是我又两边都换了串口2,可是问题就来了,在PC端的软件打不开了。可是一旦
设置minicom为串口2软件又可以打开,不知道什么原因。于是干脆狠下心来自己写串口的应用程序,具体内容见下面。
在这里有必要先了解一下底层串口的知识,
在这里用到的关于串口的分别是:termios,tcgetattr,tcsetattr,tcsendbreak,tcdrain,tcflush,tcflow,cfmakeraw,
cfgetospeed,cfgetispeed,cfsetispeed,cfsetospeed。
分别对其进行分析:
其原型为:
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, 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);
int cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(struct termios *termios_p);
speed_t cfgetospeed(struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
作用:
tcgetattr() 得到与 fd 指向的对象相关的参数,将它们保存于 termios_p 引用的 termios 结构中。函数可以从后台进程中调用;但是,终端属性可能被后来的前台进程所改变。
tcsetattr() 设置与终端相关的参数 (除非需要底层支持却无法满足)。
tcsendbreak() 传送连续的 0 值比特流,持续一段时间,如果终端使用异步串行数据传输的话。如果 duration 是 0,它至少传输 0.25 秒,不会超过 0.5 秒。如果 duration 非零,它发送的时间长度由实现定义。
如果终端并非使用异步串行数据传输,tcsendbreak() 什么都不做。
tcdrain() 等待直到所有写入 fd 引用的对象的输出都被传输。
tcflush() 丢弃要写入 引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据,取决于 queue_selector 的值:
刷新收到的数据但是不读 刷新写入的数据但是不传送 同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送。
tcflow() 挂起 fd 引用的对象上的数据传输或接收,取决于 action 的值:
挂起输出 重新开始被挂起的输出 发送一个 STOP 字符,停止终端设备向系统传送数据 发送一个 START 字符,使终端设备向系统传输数据 。
cfmakeraw()设置终端属性,就是设置termios结构中的各个参数。
cfgetospeed() 返回 termios 结构中存储的输出波特率。
cfsetospeed() 设置 termios 结构中存储的输出波特率为 speed,必须是这样的格式“B115200”。
cfsetispeed() 设置 termios 结构中存储的输入波特率为 speed。如果输入波特率被设为0,实际输入波特率将等于输出波特率。
其实就是围绕着termios这个结构捣鼓来捣鼓去,把它的数据写到寄存器中去(当然得结合硬件底层驱动)。
mycom.h头文件,声明各个函数,以及加入需要的头文件
#ifndef MYCOM_H
#define MYCOM_H
#include <stdio.h> //printf等用到
#include <fcntl.h> //file control
#include <string.h> //bzero等用到
#include <stdlib.h> //exit(0)等用到
#include <sys/times.h>//sleep等用到
#include <termios.h>//串口应用程序必须添加的头文件
#include <sys/types.h>//定义pid_t,size等
#include <unistd.h>//read,write,getpid等等
#include <sys/ioctl.h>//ioctl
typedef struct //define the uart value struct
{
char prompt; //prompt after reciving data
int baudrate; //baudrate
char databit; //data bits, 5, 6, 7, 8
char debug; //debug mode, 0: none, 1: debug
char echo; //echo mode, 0: none, 1: echo
char fctl; //flow control, 0: none, 1: hardware, 2: software
char tty; //tty: 0, 1, 2, 3, 4, 5, 6, 7
char parity; //parity 0: none, 1: odd, 2: even
char stopbit; //stop bits, 1, 2
//const int reserved; //reserved, must be zero
int reserved; //reserved, must be zero
}portinfo_t;
typedef portinfo_t *pportinfo_t; //redefine the portinfo_t
/**************** declaration the fuction to set the uart *******************/
int PortOpen(pportinfo_t pportinfo); //open the uart
int PortSet(int fdcom, const pportinfo_t pportinfo); //set the uart
void PortClose(int fdcom); //close the uart
int PortSend(int fdcom, char *data, int datalen); //send the datas
int PortRecv(int fdcom, char *data, int datalen, int baudrate);//receive the datas
#endif // MYCOM_H
mycom.h的具体.cpp文件,具体去实现各个函数的功能。需要注意的地方是,对应不同的平台TTY_DEV的定义是不同的。
#include "mycom.h"
#define TTY_DEV "/dev/ttyUSB" //端口路径 如果是在电脑端选择ttyUSB,在mini2440端则选择ttySAC
#define TIMEOUT_SEC(buflen,baud) (buflen*20/baud+2)//接收超时
#define TIMEOUT_USEC 0
/*************************************************
fuction: char *get_ptty(pportinfo_t pportinfo)
description: get thd uart name
return: ptty
**************************************************/
static char *get_ptty(pportinfo_t pportinfo)
{
char *ptty=NULL;
switch(pportinfo->tty)
{
case '0':
ptty=TTY_DEV"0";
break;
case '1':
ptty=TTY_DEV"1";
break;
case '2':
ptty=TTY_DEV"2";
break;
}
return ptty;
}
/*************************************************
fuction: convbaud(unsigned long int baudrate)
description: baud change
return: ptty
**************************************************/
static int convbaud(unsigned long int baudrate)
{
switch(baudrate){
case 2400:
return B2400;
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
default:
return B9600;
}
}
/****************************************************************
fuction: convbaud(unsigned long int baudrate)
description: set the uart
parameters: fdcom: 串口文件描述符,
pportinfo: 待设置的串口信息
return: tmp
******************************************************************/
int PortSet(int fdcom, const pportinfo_t pportinfo)
{
struct termios termios_old, termios_new;
int baudrate, tmp;
char databit, stopbit, parity, fctl;
bzero(&termios_old, sizeof(termios_old));//置字符串前sizeof个字节为零
bzero(&termios_new, sizeof(termios_new));
cfmakeraw(&termios_new);//设置终端属性
tcgetattr(fdcom, &termios_old);//get the serial port attributions
/*------------设置端口属性----------------*/
//baudrates
baudrate = convbaud(pportinfo -> baudrate);//得到波特率
cfsetospeed(&termios_new, baudrate);//填入串口输出端的波特率
termios_new.c_cflag |= CLOCAL;//控制模式,保证程序不会成为端口的占有者
// 控制模式,flow control
fctl = pportinfo-> fctl;
switch(fctl){
case '0':{
termios_new.c_cflag &= ~CRTSCTS;//no flow control
}break;
case '1':{
termios_new.c_cflag |= CRTSCTS;//hardware flow control
}break;
case '2':{
termios_new.c_iflag |= IXON | IXOFF |IXANY; //software flow control
}break;
}
//控制模式,data bits
termios_new.c_cflag &= ~CSIZE;//控制模式,屏蔽字符大小位
databit = pportinfo -> databit;
switch(databit){
case '5':
termios_new.c_cflag |= CS5;
case '6':
termios_new.c_cflag |= CS6;
case '7':
termios_new.c_cflag |= CS7;
default:
termios_new.c_cflag |= CS8;
}
//控制模式 parity check
parity = pportinfo -> parity;
switch(parity){
case '0':{
termios_new.c_cflag &= ~PARENB;//no parity check
}break;
case '1':{
termios_new.c_cflag |= PARENB;//odd check
termios_new.c_cflag &= ~PARODD;
}break;
case '2':{
termios_new.c_cflag |= PARENB;//even check
termios_new.c_cflag |= PARODD;
}break;
}
//控制模式,stop bits
stopbit = pportinfo -> stopbit;
if(stopbit == '2'){
termios_new.c_cflag |= CSTOPB;//2 stop bits
}
else{
termios_new.c_cflag &= ~CSTOPB;//1 stop bits
}
//other attributions default
termios_new.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//输入模式,原始数据输入
tcflush(fdcom, TCIFLUSH);//溢出的数据可以接收,但不读
tmp = tcsetattr(fdcom, TCSANOW, &termios_new);//设置新属性,TCSANOW:所有改变立即生效 tcgetattr(fdcom, &termios_old);
return(tmp);
}
/****************************************************************
fuction: PortOpen(pportinfo_t pportinfo)
description: open serial port
parameters: pportinfo: 待设置的串口信息
return: fdcom:文件描述符
******************************************************************/
int PortOpen(pportinfo_t pportinfo)
{
int fdcom; //串口文件描述符
char *ptty;
ptty = get_ptty(pportinfo);
//fdcom = open(ptty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
fdcom = open(ptty, O_RDWR | O_NOCTTY | O_NONBLOCK);
return (fdcom);
}
/****************************************************************
fuction: PortClose(int fdcom)
description: close serial port
parameters: fdcom:文件描述符
return: NONE
******************************************************************/
void PortClose(int fdcom)
{
close(fdcom);
}
/****************************************************************
fuction: PortSend(int fdcom, char *data, int datalen)
description: close serial port
parameters: fdcom: 串口描述符,data: 待发送数据,datalen: 数据长度
return: len:实际长度
******************************************************************/
int PortSend(int fdcom, char *data, int datalen)
{
int len = 0;
len = write(fdcom, data, datalen);//实际写入的长度
if(len == datalen){
return (len);
}
else{
tcflush(fdcom, TCOFLUSH);
return -1;
}
}
/****************************************************************
fuction: PortRecv(int fdcom, char *data, int datalen, int baudrate)
description: receive the datas
parameters: fdcom:串口描述符, data:接收缓冲区, datalen:接收长度,
baudrate:波特率
return: readlen:实际读入的字节数
******************************************************************/
int PortRecv(int fdcom, char *data, int datalen, int baudrate)
{
int readlen, fs_sel;
fd_set fs_read;
struct timeval tv_timeout;
FD_ZERO(&fs_read);
FD_SET(fdcom, &fs_read);
tv_timeout.tv_sec = TIMEOUT_SEC(datalen, baudrate);
tv_timeout.tv_usec = TIMEOUT_USEC;
fs_sel = select(fdcom+1, &fs_read, NULL, NULL, &tv_timeout);
if(fs_sel){
readlen = read(fdcom, data, datalen);
return(readlen);
}
else{
return(-1);
}
return (readlen);
}
总算到Qt端了:
serial.h头文件,声明各个控件以及引用mycom.h,定义关于串口的各个槽函数
#ifndef SERIAL_H
#define SERIAL_H
#include <QtGui>
#include "mycom.h"
class serial : public QWidget
{
Q_OBJECT
public:
serial(QWidget *parent = 0);
~serial();
void layout();
void init_serial();
public:
QTextBrowser *textbrowser;
QLineEdit *lineedit;
QPushButton *open_button;
QPushButton *close_button;
QPushButton *send_button;
QLabel *label1;
QLabel *label2;
public slots:
void open_serial();
void close_serial();
void read_serial();
void send_setial();
};
#endif // SERIAL_H
serial.h的具体实现,要注意的地方是数据类型的转换,QLineEdit的text().toASCII返回的是QByteArray,而PortSend要发送的是
char类型的数据,所以就需要转换,在Qt中使用.data()就可以实现转换。还有要注意的地方就是QSocketNotifier不要忘记添加了。
#include "serial.h"
portinfo_t portinfo = {
'0', // print prompt after receiving
115200, // baudrate: 115200
'8', // databit 8
'0', // debug: off
'0', // echo off
'2', // flow control: software
'0', // default tt: SAC0
'0', // parity: none
'1', // stopbit: 1
0 // reserved
};
static int serial_fd;
char databuffer[20];
int datalen;
serial::serial(QWidget *parent)
: QWidget(parent)
{
layout();//lay out
open_button->setEnabled(true);
close_button->setEnabled(false);
send_button->setEnabled(false);
connect(open_button,SIGNAL(clicked()),this,SLOT(open_serial()));
connect(close_button,SIGNAL(clicked()),this,SLOT(close_serial()));
connect(send_button,SIGNAL(clicked()),this,SLOT(send_setial()));
QSocketNotifier *notifier=new QSocketNotifier(serial_fd,QSocketNotifier::Read,this);
connect(notifier,SIGNAL(activated(int)),this,SLOT(read_serial()));
read_serial();
}
serial::~serial()
{
}
void serial::init_serial()
{
}
void serial::open_serial()
{
serial_fd=PortOpen(&portinfo);
if(serial_fd<0)
{
perror("Error:open serial port error");
//exit(1);
}
PortSet(serial_fd,&portinfo);
open_button->setEnabled(false);
close_button->setEnabled(true);
send_button->setEnabled(true);
}
void serial::close_serial()
{
PortClose(serial_fd);
open_button->setEnabled(true);
close_button->setEnabled(false);
send_button->setEnabled(false);
}
void serial::read_serial()
{
datalen=PortRecv(serial_fd,databuffer,10,115200);
textbrowser->insertPlainText(databuffer);
}
void serial::send_setial()
{
QByteArray temp=lineedit->text().toAscii();
PortSend(serial_fd,temp.data(),10);
}
void serial::layout()
{
label1=new QLabel("Receive:");
label2=new QLabel("Send:");
open_button=new QPushButton("Open");
close_button=new QPushButton("Close");
send_button=new QPushButton("Send");
textbrowser=new QTextBrowser(this);
lineedit=new QLineEdit;
lineedit->setText("123489");
QVBoxLayout *vlayout1=new QVBoxLayout;
vlayout1->addWidget(label1);
vlayout1->addWidget(textbrowser);
vlayout1->addWidget(label2);
QVBoxLayout *vlayout3=new QVBoxLayout;
vlayout3->addWidget(open_button);
vlayout3->addWidget(close_button);
QHBoxLayout *hlayout2=new QHBoxLayout;
hlayout2->addLayout(vlayout1);
hlayout2->addLayout(vlayout3);
QHBoxLayout *hlayout1=new QHBoxLayout;
hlayout1->addWidget(lineedit);
hlayout1->addWidget(send_button);
QVBoxLayout *vlayout2=new QVBoxLayout;
vlayout2->addLayout(hlayout2);
vlayout2->addLayout(hlayout1);
setLayout(vlayout2);
}
总结:总算完成了,前后花了两天的时候,很高兴的是自己没有放弃。至于这个串口的QT有什么用,那作用老大了,以前我用VB写过环境
监控系统,在PC端实现的,低下用的是ADUC824,那代码,叫宏伟啊。Qt就简单多了(站在巨人的肩膀上)。有了UART的通信,一些
控制类的东西都可以实现了,而且界面还可以做得要多漂亮就有多漂亮(看个人的了)。