一、课程设计总体内容
从串口读取传感器(温湿度、光照)发送的气温信息,然后显示在界面上。同时用户的操作、气温的异常也会显示界面的日志模块中,而且也会记录在文件中。气温的异常会引起拍照功能(用来记录当时的场景信息),当然也可以用用户手动拍照。拍照的内容储存在指定的文件夹中,可以切换到另一界面播放拍照的图片。
二、课程设计模块框架
各模块联系如下结构所示:
三、实验模块介绍和代码分析
下面分模块进行介绍:
温湿度模块:
从串口读取数据,然后经过解析,提取出温度、湿度、光照度后,显示在界面的文本框中。传感器发送的数据是按照其自身的协议规定好的。对于串口的收发,此处采用的是第三方类库,其实QT5以后已经自带了串口类,完全可以不用第三方类库。思路上是从串口收发一定的字符串数据,然后进行字符串提取,按照协议进行分析,最后计算出温湿度信息。
(1)气温信息模块
//设置Weather信息
void weather::SetData(QVector<QString> data)
{
if(data.isEmpty() == false)
{
First = data[0];
Mes_len = data[1];
Nod_id = data[2];
Mod_id = data[3];
Order = data[4];
XH = data[5];
XL = data[6];
YH = data[7];
YL = data[8];
GH = data[9];
GL = data[10];
}
}
代码分析:以上代码,把从串口收到的数据分字段赋给相应的变量。
//计算温度
int weather::Get_temperature()
{
if(XL.isEmpty() == false && XH.isEmpty() == false)
{
int num1[2];
Inv_int_num(XL,num1);
int num2[2];
Inv_int_num(XH,num2);
int tem = (num2[0]* 16 + num2[1]) * 256 + (num1[0] * 16 + num1[1]);
return tem;
}
return 0;
}
代码分析:根据传感器给的zigbeee协议,计算出温度信息。同理,可以计算出湿度和光照信息。
(2).日志模块:
当计算出的气温信息,不在正常的范围内时,将会把异常的气温信息同步记录到界面文本框和日志文件中去。
// 设置气温限制信息,即气温值不在此范围则会记录到日志中
void Logs::SetLimitInfo()
{
low_tem_limit = 20;
high_tem_limit = 30;
low_hum_limit = 0.2;
high_hum_limit = 0.8;
low_lig_limit = 20;
high_lig_limit = 50;
}
//向日志中记录信息
void Logs::WriteWeaLogs(int temp,int hum,double lig)
{
QDateTime current_time = QDateTime::currentDateTime();
QString current_time_str = current_time.toString("yyyy-MM-dd hh:mm:ss ddd");
QString logs_temp = NULL,logs_hum = NULL ,logs_lig = NULL;
if(temp < low_tem_limit)
logs_temp = "The temperature below "+QString::number(low_tem_limit)+" at "+current_time_str+
" And It's "+QString::number(temp)+".";
else if(temp > high_tem_limit)
logs_temp = "The temperature over "+QString::number(low_tem_limit)+" at "+current_time_str+
" And It's "+QString::number(temp)+".";
if(hum < low_hum_limit)
logs_hum = "The humidity belows "+QString::number(low_hum_limit)+" at "+current_time_str+
" And It's "+QString::number(hum)+".";
else if(hum > high_hum_limit)
logs_hum = "The humidity over "+QString::number(low_hum_limit)+" at "+current_time_str+
" And It's "+QString::number(hum)+".";
if(lig < low_lig_limit)
logs_lig = "The light intancity belows "+QString::number(low_lig_limit)+" at "+current_time_str+
" And It's "+QString::number(lig)+".";
else if(lig > high_lig_limit)
logs_lig = "The light intancity over "+QString::number(low_lig_limit)+" at "+current_time_str+
" And It's "+QString::number(lig)+".";
WriteLogs(logs_temp);
WriteLogs(logs_hum);
WriteLogs(logs_lig);
}
代码分析:以上代码,通过判断所给的气温信息是否在限制的范围内,如果不在的话,则向日志中写入其相关信息。 WriteLogs(string data)是文件操作,即向文件中写入data数据。
(3)、拍照模块
拍照模块需要外接摄像头(或者pc自带的应该也行)。此处拍照功能是直接调用用别人写好的V4L2类中的函数。但是,因为拍照比较耗时,所以需要在线程中操作,此处用的是movetoThread方法。
//拍照
void PhotoWorker::TakingPhoto(QString path)
{
is_ok = false;
QByteArray ba = path.toLatin1();
//此函数进行拍照
if( tak_pht_v4l->Save_BMP(ba.data()))
is_ok = true;
//返回拍照是否成功信号
emit(Is_Ok(is_ok));
}
代码分析:以上代码即是进行拍照的功能。过程是,当在MainWindow中如果判断天气数据不再指定的范围内,则发送拍照信号。PhotoWorker类中的TakingPhoto()函数接受到此信号后,进行拍照功能(也就是保存一帧图片)。注意,拍照功能是通过多线程实现的,因为在主线程中还在不断接受串口数据。
(4)、照片显示模块:
使用Label进行图片的显示。然后在代码中调用定时器功能,达到每隔一定时间进行刷新,达到循环播放图片的目的。另外,本实验中使用了三个button,用来对电子相框进行控制,其功能是上一张、播放/暂停、下一张。对于显示图片,还有一点要说明的是,本实验并没有一下把所有的图片都读到内存中,而是定时器时间一到,就从指定目录中搜索指定的图片,将其显示,因此,图片的编号是有一定要求的,是从1.bmp、2.bmp依次递增类型。
Pic::Pic(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Pic)
{
ui->setupUi(this)
base_path = "./pic/"; //基路径
which_pic = 1; //用于显示目前第几张图片
is_play = false; //用于判断是处于播放状态、还是停止状态
num_of_pic = 5; //图片的初始个数,会随着拍照增加
show_timer = new QTimer(this); //播放图片定时器
count_timer = new Qtimer(this); //用于确定图片目录中有张图片
//播放定时器与播放槽函数连接
connect(show_timer,SIGNAL(timeout()), this, SLOT(on_play_button_clicked()));
//确定图片数量的定时器与setNumOfPic槽函数连接
connect(count_timer,SIGNAL(timeout()),this,SLOT(setNumOfPic()));
count_timer->start(800);
//用于填充图片数据到label中
FillIma(which_pic,ui->show_label);
//show-label是在界面文件中设置的label名称
ui->show_label->show();
ui->show_label->setScaledContents(true);
}
代码分析:以上代码为Pic类的构造函数,在这个构造函数里进行里基本的槽与函数连接,设定定时器等基本功能。
//将图片填充至label
void Pic::FillIma(int which_pic, QLabel *show_label)
{
QString image_path = base_path+QString::number(which_pic)+".bmp"; //定义图片的路径,为基路径加上后缀名
QPixmap pixmap = (image_path); //装入图片
if(pixmap.isNull())
{
show_label->setText("There is no picture."); //如果没有图片,则显示无此图片
}
else
{
show_label→setPixmap(pixmap); //如果有图片则填充到label中
}
}
代码分析:以上代码,是将指定目录下的图片填充至Label中,用的是setPixmap函数。
//显示上一张图片
void Pic::on_pre_button_clicked()
{
//如果已经到了第一张图片则从最后一张图片显示
if(which_pic-1 <= 0)
which_pic = num_of_pic;
else
which_pic--;
FillIma(which_pic,ui->show_label);
ui->show_label->show();
}
// 显示下一张图片
void Pic::on_next_button_clicked()
{
//如果已经到了最后一张图片则从第一张图片开始显示
if(which_pic + 1 > num_of_pic)
which_pic = 1;
else
which_pic++;
FillIma(which_pic,ui->show_label);
ui->show_label->show();
}
代码分析:以上代码很简单,就是点击按钮之后,触发上面的函数,将当前要显示的图片显示出来。
void Pic::on_play_button_clicked()
{
QObject *obj_sender = sender(); //如果判断触发此函数的是哪个信号
QObject *com_obj = (QObject*)ui->play_button;
if(obj_sender == com_obj) //如果是button,则改变play或者stop状态
{
if(is_play == true)
{
is_play = false;
show_timer->stop();
}
else
{
is_play = true;
show_timer->start(1000);
}
}
if(is_play == true) //如果是播放状态,则播放下一张图片
on_next_button_clicked();
}
代码分析:以上代码,有点难懂。首先判断触发的是那个信号,用的是send()函数,此函数是系统提供的,返回的是一个obj对象指针,我们可以用这个指针判断到底是哪个对象触发了发出了信号。为什么要判断呢?因为有两个对象发出的信号都能触发这个函数,一个是show-timer信号,一个是点击play按钮的信号。(其实,也可以分别用两个定时器来实现,不过定时器在系统中是稀缺资源,得珍惜着用)
void Pic::setNumOfPic()
{
//用来查找指定目录下的文件(图片)个数
QString path = "./pic"; //路径
QDir *dir=new QDir(path);
QStringList filter; //过滤器
filter<<"*.bmp"; //设置过滤器内容
dir->setNameFilters(filter);
QList<QFileInfo> *fileInfo=new QList<QFileInfo>(dir->entryInfoList(filter));
num_of_pic = fileInfo->count();
}
代码分析:上面的代码是用一个单独的定时器来每隔一定时间判断目录下的图片个数(因为拍照会增加图片的数量)。
(5)、界面切换:
这次任务设计了两个界面,一个界面就是显示照片界面(如下图一)。另一个界面用来显示温湿度等信息。涉及到两个窗口,所以就需要进行窗口的切换。在此出使用的是堆栈切换窗口的方式。即一开始把所有的窗口都实例化后压入栈中。在需要的时候,发送一个信号,使相应的窗口调到栈顶,显示出来。
widge::widge(QWidget *parent) :
QWidget(parent)
{
setFixedSize(800, 600);
mainwindow = new MainWindow;
pic = new Pic;
stackLayout = new QStackedLayout;
stackLayout->addWidget(mainwindow);
stackLayout->addWidget(pic);
connect(mainwindow,SIGNAL(display(int)),stackLayout,SLOT(setCurrentIndex(int)));
connect(pic,SIGNAL(display(int)),stackLayout,SLOT(setCurrentIndex(int)));
mainLayout = new QVBoxLayout;
mainLayout->addLayout(stackLayout);
setLayout(mainLayout);
}
四、实验结果截图
五、实验总结
本次课程设计中,出现了很多问题。可能是由于自己以前学的不扎实。先是串口读不出数据,后来好不容易读出数据了,读出的数据又是错误的。(最后才发现是串口线的问题)。随着我不断查阅资料、讨论和尝试不同的方法,最终找到了合适的解决方案。通过这次实验,我深刻认识到了嵌入式系统的重要性和应用价值。嵌入式系统不仅在智能家居、智能交通等领域有着广泛的应用,还涉及到国家安全、军事等领域。未来,随着物联网、人工智能等技术的不断发展,嵌入式系统的应用前景将更加广阔。最后,我对实验进行了总结和反思。我认为,要想成为一名优秀的嵌入式系统工程师,不仅需要具备扎实的理论基础,还需要具备丰富的实践经验。在未来,我将继续努力学习嵌入式系统的相关知识,不断提高自己的技能水平,为嵌入式系统的发展做出更大的贡献。