Linux下 QT串口与51单片机通信实例
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
QT串口与51单片机通信
通过这个小例子主要想说明QT怎样进行线程编程的思想,实例如图,好吧,下面是过程
上一个例子我们采用的是手工编写代码的方法,这个例子我们来玩一下designer,其实Qt4己经把界面与功能分开了,用designer来进行界面
设计,再手工编写一些功能,如信号与槽,这样开发效率会大大提高,呵呵,开一个终端,输入/usr/local/Trolltech/Qt-4.5.1/bin/designer
,如果第一次打开出现字体不对,可以打开qtconfig进行一些相关配置,打开后我们新建一个Main Window,在右边的属性框中设置一下界面大小,
1.我ARM板的LCD大小为320x240,所以我也设为320x240;
2.左边是一些我们常用的窗口部件,这里我们用到一个lable标签来做显示,再放几个pushButton按钮,在属性objectName重新更改它的名字,改为我们记得的,这样在写功能时记得哪个按钮叫什么名字,对于一个初学QT的人来说,很想知道每一个部件到底有什么信号和槽,别急,我们可以这样来看,选中一个lable,按F4,再点击lable拖动出现接地符号时松开,弹出编辑信号与槽,这时左边列出的是信号,右边为槽,这里我们不用配置连接,等下我们再手工写,
3最后我们用到一个lable标签和三个pushButton按钮,并命名为dis_label、writeButton、readButton、closeButton,然后保存为mainwindow.ui,这样designer就完工了,呵呵..
4.下面我们编写一个线程,用于管理串口收发工作,它不涉及到任何界面,只做好它的本份工作就得了,编写一个thread.h文件gedit thread.h,
#ifndef THREAD_H
#define THREAD_H
#include<QThread>
class Thread:public QThread
{
Q_OBJECT
public:
Thread();
char buf[128];
volatile bool stopped;
volatile bool write_rs;
volatile bool read_rs;
protected:
virtual void run();
};
#endif
我们定义一个Thread类,它继承于QThread,看到只设有一些变量和一个run函数,virtual表示为虚函数,你也可以去掉,加上去会增加一些内存开销,
但提高了效率,对于这个小程序是看不出什么效果的,volatile这个大家都懂了吧,就是防止偷懒,呵呵,
5.再看看thread.cpp
#include"thread.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h> //串口用到的
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#define BAUDRATE B9600
//#define RS_DEVICE "/dev/ttyS0" //串口1
#define RS_DEVICE "/dev/ttySAC1" //串口1
Thread::Thread()
{} //析构
void Thread::run() //这就是线程的具体工作了
int fd,c=0,res;
struct termios oldtio,newtio; //termios结构是用来保存波特率、字符大小等
printf("start...\n");
fd=open(RS_DEVICE,O_RDWR|O_NOCTTY); //以读写方式打开串口。
不控制TTY
if(fd<0)
{
perror("error");
exit(1); //失败退出
}
printf("Open...\n");
tcgetattr(fd,&oldtio); //保存当前设置到oldtio
bzero(&newtio,sizeof(newtio)); //清除newtio结构,并重新对它的成员设置如下
newtio.c_cflag=BAUDRATE|CS8|CLOCAL|CREAD; //9600、8位、忽略DCD信号、启用接收装置
newtio.c_iflag|=IGNPAR; //忽略奇偶
newtio.c_oflag=0;
newtio.c_lflag=0;
newtio.c_cc[VMIN]=0;
newtio.c_cc[VTIME]=100; //在规定时间(VTIME)内读取(VMIN)个字符;
tcflush(fd,TCIFLUSH); //清除所有队列在串口的输入与输出;
tcsetattr(fd,TCSANOW,&newtio); //把我们的设置写入termios
while(stopped) //stopped为0时将退出线程
{
if(write_rs) //write_rs为1时把字符串从串口中输出
{
write_rs=0;
write(fd,"QtEmbedded-4.5.1",16);
}
if(read_rs) //read_rs为1时读取,并存在buf
{
read_rs=0;
res=read(fd,buf,10);
buf[res]=0;
emit finished(); //读完后发一个信号
}
}
printf("Close...\n");
tcsetattr(fd,TCSANOW,&oldtio); //重新设置回原来的
close(fd);
quit();
}
QT有terminated()和wait函数来停止或暂停线程,为什么不用呢,具书上说会造成阻塞什么的,呵呵,我也不懂,所以我就用stopped变量来控制,让
它死循环在那里,其它我已经在注释上说明了,应该没什么问题了吧,没有的话再写一个.
6.mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include<QtGui>
#include"ui_mainwindow.h" //奇怪?这个头文件从哪里来的?呵呵,刚才用designer做的mainwindow.ui文件,经过make后会生成这个头文件,
#include"thread.h" //把我们前面定义的线程包含进来
class MainWindow:public QMainWindow,public Ui::MainWindow //多继承
{
Q_OBJECT
public:
MainWindow(QWidget *parent=0);
public slots:
void writeThread();
void readThread();
void closeThread();
void display();
private:
Thread *yy;
};
#endif
MainWindow继承于QMainWindow和MainWindow,即多继承,对于不是很复杂的程序,用多继承是一个较好的方法,如果程序较复杂,还是用单继承了,
我们再看一下mainwindow.cpp,看这些槽具有什么功能:
#include"mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
:QMainWindow(parent)
{
setupUi(this);
yy = new Thread;
yy->start(); //启动线程
yy->stopped=1; //初始化变量
yy->write_rs=0;
yy->read_rs=0;
connect(writeButton,SIGNAL(clicked()),this,SLOT(writeThread())); //信号与槽
connect(readButton,SIGNAL(clicked()),this,SLOT(readThread()));
connect(closeButton,SIGNAL(clicked()),this,SLOT(closeThread()));
connect(yy,SIGNAL(finished()),this,SLOT(display())); //前面线程读完了不是发一个信号么,这个信号就是发到这个槽
}
void MainWindow::display()
{
dis_label->setText(yy->buf); //读到的在dis_label显示,dis_label就是我们前面designer放的标签,
}
void MainWindow::writeThread() //前面线程都是根据stopped、write_rs、read_rs的状态来工作的^_^
{
yy->write_rs=1;
}
void MainWindow::readThread()
{
yy->read_rs=1;
}
void MainWindow::closeThread()
{
yy->stopped=0;
}
还差个main.cpp就完工了,哈哈...
#include<QApplication>
#include"mainwindow.h"
int main(int argc,char *argv[])
{
QApplication app(argc,argv);
MainWindow mw;
mw.show();
return app.exec();
}
啊!终于完成了,不!这只是完成了ARM这边,还有MCU这边,呵呵,很久不玩单片机了,下面为AT89S51编一个串口程序,并用1602来显示:
/**************************************************************/
/* 三贱客龙& 07机电(1)班*/
/* 目标器件AT89S51 */
/* 晶振11.0592 MHZ */
/* 编译环境keil uVsion3 */
/* 适用于51综合开发实验板*/
/**************************************************************/
/********************** 1602 ********************************/
/**************一定要关了上拉电阻再下载,有干拢****************/
#include <reg51.h>
#include <intrins.h>
typedef unsigned char BYTE; //类型定义,编译时再处理
typedef bit BOOL ; //位
/****************串口部份**********************************/
unsigned char key_s, key_v, tmp;
sbit K1 = P1^4;
BOOL flag1=0;
BYTE i=0;
BYTE z=0;
void isr_uart(void); //串口中断服务函数
void send_str(); // 传送字串
bit scan_key(); // 扫描按键
void proc_key(); // 键处理
void delayms(unsigned char ms);
com_init() //串口初始部分
{
TMOD = 0x20; // 定时器1工作于8位自动重载模式, 用于产生波特率方式2
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
SCON = 0x50; // 设定串行口工作方式为1且没有校验位
PCON=0X00; // 波特率不倍增,倍增时为0x80
TR1 = 1; // 启动定时器1运行
ET1 = 0; // 禁T1定时器产生中断
ES=1; //允串口中断
EA=1; //开总中断
}
/****************************串口部分************************************/
sbit rs = P2^0; // 寄存器选择
sbit rw = P2^1; //读写信号线
sbit ep = P2^2; //使能端
sbit right = P2^6; //背光
char idata display[16] = {"zhilong2382@163"}; //开机时显示的字符串
char code str[] = {"xxxI Love you ! "}; //按K1时将发送这个内容,由于前3位对方总是收不到,这里用xxx干掉了,用串口调试助手也一样,
delay(BYTE ms)
{ // 延时子程序
BYTE i;
while(ms--)
{
for(i = 0; i< 250; i++)
{
_nop_();
_nop_();
_nop_();
_nop_();
}
}
}
BOOL lcd_bz() //只检测最高位
{ // 测试LCD忙碌状态
BOOL result;
rs = 0; //指令寄存器
rw = 1; //读
ep = 1; //接收指令,为下降沿做准备
_nop_(); //空语句,延时约1US
_nop_();
_nop_();
_nop_();
result = (BOOL)(P0 & 0x80); //取最高位
ep = 0; //执行命令
return result;
}
/************** 写入指令数据到LCD **************/
lcd_wcmd(BYTE cmd)
{
while(lcd_bz()); //忙时执行空语句
rs = 0;
rw = 0;
ep = 0;
_nop_();
_nop_();
P0 = cmd;
_nop_();
_nop_();
_nop_();
_nop_();
ep = 1;
_nop_();
_nop_();
_nop_();
_nop_();
ep = 0;
}
lcd_pos(BYTE pos)
{ //设定显示位置
lcd_wcmd(pos | 0x80); //指令8
}
lcd_wdat(BYTE dat) //写入字符显示数据到LCD { //写入要显示的字符串
while(lcd_bz()); //读忙
rs = 1; //写数据命令
rw = 0;
ep = 0;
P0 = dat;
_nop_();
_nop_();
_nop_();
_nop_();
ep = 1;
_nop_();
_nop_();
_nop_();
_nop_();
ep = 0;
}
lcd_init()
{ //LCD初始化设定
lcd_wcmd(0x38); //指令6,不检测忙信号
delay(1);
lcd_wcmd(0x0c); //指令4,显示开/关控制
delay(1);
lcd_wcmd(0x06); //指令3,输入模式
delay(1);
lcd_wcmd(0x01); //清除LCD的显示内容
delay(1);
}
/*****************************************main********************************/ main()
{
BYTE i,j; j=0;
right=0; //开背光
lcd_init(); // LCD
delay(10);
com_init(); // 初始化串口
/*************** 显示内容及方式*********************/
while(1)
{
if(scan_key()) // 扫描按键第一遍为真
{
delayms(10); // 延时去抖动
if(scan_key()) // 再次扫描
{
key_v = key_s; // 保存键值
proc_key(); // 键处理
}
}
lcd_wcmd(0x01); //清除LCD的显示内容
delay(1);
lcd_pos(0); // 设置显示位置为第一行的第3个字符
i = 0;
while(display[i] != '\0') //是否到字符串末尾
{ // 显示字符
lcd_wdat(display[i]);
i++; delay(70);
}
}
}
// 扫描按键
bit scan_key()
{
key_s = 0x00;
key_s |= K1; //字节与位运算改变的是最低位
return(key_s ^ key_v);
}
// 键处理
void proc_key()
{
if((key_v & 0x01) == 0)
{ // K1按下
TI=1; //send_str(); // 传送字串到PC
}
}
// 每次传送完一串字符
void send_str()
{
unsigned char i = 0;
while(str[i] != '\0')
{
SBUF = str[i];
while(!TI); // 等特数据传送,发完后TI=1
TI = 0; // 清除数据传送标志
i++; // 下一个字符
}
}
// 延时子程序
void delayms(unsigned char ms)
{
unsigned char i;
while(ms--)
{
for(i = 0; i < 120; i++);
}
}
void isr_uart(void) interrupt 4 //串口服务函数
{
if(RI==1)
{ RI=0;
display[z]=SBUF;
z++;
if(z>=16)
z=0;
}
if(TI==1)
send_str();
}
呵呵,这个是我大一时学单片机时弄的,看起来乱乱的,但我保证绝对能用,程序编译后把hex 文件烧到单片机,插上串口线,串口线我是自己做的,
注意有直通与交叉之分,一切准备好后,通电...怎么样,当在ARM板上点击write按钮时,将发送QtEmbedded-4.5.1给单片机,并在1602上显示
出来,点read后,再按下p1.4键时(按久点,因为我是用查询方法读键),单片机发送xxxI Love you !给ARM,ARM板上显示I Love you !,
怎么样,是不是更爽了,是的话我们再编一个复杂点的例子,编什么呢?PWM吧,可以用来控
制电机的转速,我是学机电的,也就是我的专业是机电一体化,
是不是很奇怪学机电的也搞程序?哈哈..兴趣!!这个例子为在QT上可以改变频率、占空比,并有语音提示,还专门编了个数字小键盘方便输入,代码下次再贴了。