0%

简历项目总结

1. Qt上位机

分为两个线程,分别是采集线程和绘制线程。

1.1 采集线程

采集线程面向过程,从buffer(C风格数组)中读取数据,并写入txt文本文件中。数据采集卡需要读取三个通道,分别代表编码器的 A 相,编码器的 B 相,以及同步信号。缓冲区的数据是同一时刻三个通道的数据是相邻的。然后通过 A 相和 B 相的脉冲数计算转过的角度,A 相在 B 相前角度就是正,A 相在 B 相后角度就是负。记录当前时间与开始时间的差值,作为绘制的 x 坐标,记录当前的角度,作为 y 坐标。这个 x 坐标和 y 坐标是 samplingForce 这个对象的成员变量。

ui 线程通过访问这个 samplingForce 的类成员来获取绘制的点。这中间涉及到线程安全问题以及面向对象的问题。

1.2 UI 采集线程

首先是通过 QChart 来绘制界面。通过 QPen 来设置曲线的参数,添加 QLineSeries 曲线用于指示,添加 QSplineSeries 用于绘制,这个是平滑曲线。这两条曲线要设置曲线的绘制参数,把前面的 pen 设置进去。然后就是要新建两个坐标轴,并把这两条坐标轴添加到创建的 QChart 里面,同时也要把两条曲线添加到 QChart 里面。最后再把坐标轴和曲线关联起来。

通过定时器来不断地往曲线上添加点,这里需要读取采集线程给出的绘制点。为了防止临界资源的冲突,这里肯定不能只加一般的锁因为采集线程不能阻塞,阻塞了就会丢点。所以需要构建一个共享类来分别处理读和写的情况,总结来说要注意以下几点:

  • 采集线程要修改数据,所以调用共享类修改数值的成员函数,这个函数要使用写锁QWriteLocker locker(&rwLock);
  • ui绘制线程要访问数据,所以调用共享类返回数值的成员函数,这个函数要使用读锁QReadLocker locker(const_cast<QReadWriteLock*>(&rwLock));,同时添加const关键字修饰,以确保数据不被修改。
  • Qt 中的读写锁为QReadWriteLock rwLock

在Qt中实现读写锁可以通过使用QReadWriteLock类来完成。QReadWriteLock提供了一个读写锁机制,允许多个读线程同时访问资源,但写线程访问资源时必须独占该资源。下面是一个简单的示例,展示了如何在Qt中使用读写锁来保护共享数据,以确保线程安全。

示例代码

假设我们有一个简单的应用程序,包含一个采集线程和一个UI线程,它们需要共享一些数据。

共享数据类

首先,定义一个包含共享数据的类,并使用QReadWriteLock来保护数据的访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <QReadWriteLock>
#include <QMutex>
#include <QThread>

class SharedData {
public:
SharedData() : counter(0) {}

void incrementCounter() {
QWriteLocker locker(&rwLock);
++counter;
}

int getCounter() const {
QReadLocker locker(const_cast<QReadWriteLock*>(&rwLock));
return counter;
}

private:
mutable QReadWriteLock rwLock; // 必须是mutable,以便const成员函数中可以使用
int counter;
};

采集线程

接下来,定义一个采集线程,该线程定期更新共享数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <QThread>
#include "SharedData.h"

class CollectorThread : public QThread {
public:
CollectorThread(SharedData *data) : data(data) {}

protected:
void run() override {
while (true) {
data->incrementCounter();
msleep(1000); // 模拟采集过程,每秒更新一次
}
}

private:
SharedData *data;
};

UI线程

定义一个UI线程,该线程定期从共享数据中读取值并显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <QTimer>
#include <QWidget>
#include <QLabel>
#include "SharedData.h"

class MainWindow : public QWidget {
Q_OBJECT

public:
MainWindow(SharedData *data, QWidget *parent = nullptr) : QWidget(parent), data(data) {
label = new QLabel("Counter: 0", this);
setLayout(new QVBoxLayout());
layout()->addWidget(label);

QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::updateLabel);
timer->start(1000); // 每秒更新一次
}

private slots:
void updateLabel() {
int value = data->getCounter();
label->setText(QString("Counter: %1").arg(value));
}

private:
SharedData *data;
QLabel *label;
};

主函数

最后,在主函数中启动采集线程和UI线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <QApplication>
#include "MainWindow.h"
#include "CollectorThread.h"
#include "SharedData.h"

int main(int argc, char *argv[]) {
QApplication app(argc, argv);

SharedData data;
CollectorThread collectorThread(&data);
collectorThread.start();

MainWindow mainWindow(&data);
mainWindow.show();

return app.exec();
}

说明

  • QReadWriteLock:用于保护共享数据的读写操作。
  • QWriteLockerQReadLocker:分别用于写操作和读操作的锁定。QWriteLocker会在构造时自动获取写锁,在析构时释放写锁;QReadLocker则是在构造时获取读锁,在析构时释放读锁。
  • CollectorThread:模拟数据采集过程,每秒更新一次共享数据。
  • MainWindow:模拟UI线程,每秒从共享数据中读取值并更新显示。

通过这种方式,可以有效地实现线程间的同步,确保数据的一致性和线程的安全性。

2. ROS篮球机器人

3. SRTP项目

分为移动监测和数据分析两个部分。

3.1 移动监测

设计了基于 stm32 的原型机,主要有以下几个主要组成部分:

  • M702七合一空气传感器:二氧化碳浓度(ppm),甲醛浓度(ug/m³),TVOC(总挥发性有机物)浓度(ug/m³),PM2.5浓度(ug/m³), PM10浓度(ug/m³),温度(℃),湿度(%)
  • ATK M751 4G DTU
  • STM32单片机
  • GPS/BD 双模定位模块

进行了集成化,同时选用了功耗更低的模块:

  • GPRS模块 SIM800C:SIM800C是一款四频GSM/GPRS模块,为城堡孔封装。其性能稳定,外观小巧,性价比高,能满足客户的多种需求。SIM800C工作频率为GSM/GPRS850/900/1800/1900MHz,可以低功耗实现语音、SMS和数据信息的传输。

    特性 说明
    省电 SLEEP模式下的耗流为0.6毫安 (AT+CFUN=0 )
    GPRS数据特性 GPRS 数据下行传输:最大 85.6 kbpsGPRS
    数据上行传输:最大 85.6 kbps
    编码格式:CS-1, CS-2, CS-3 和 CS-4
    支持通常用于PPP连接的PAP(密码验证协议)协议
    内嵌 TCP/IP 协议
    支持分组广播控制信道(PBCCH)
  • GPS模块 Ublox M8:

  • M702空气传感器

  • STM32单片机

TCP服务器配置

使用记事本打开并修改SD卡里的TCPconfig.cfg文件,其第一行为TCP服务器IP地址,第二行为端口号,配置好后将SD卡插回PCB板,将各个模块连接好通电即可。

字节 名称 说明
B1 帧头 1 固定值 3Ch
B2 帧头 2 固定值 02h
B3 数据 eCO2 高字节
B4 数据 eCO2 低字节
B5 数据 eCH2O 高字节
B6 数据 eCH2O 低字节
B7 数据 TVOC 高字节
B8 数据 TVOC 低字节
B9 数据 PM2.5 高字节
B10 数据 PM2.5 低字节
B11 数据 PM10 高字节
B12 数据 PM10 低字节
B13 数据 Temperature 整数部分
B14 数据 Temperature 小数部分
B15 数据 Humidity 整数部分
B16 数据 Humidity 小数部分
B17 校验和 校验和
B18 数据 纬度最低字节
B19 数据 纬度低字节
B20 数据 纬度高字节
B21 数据 纬度最高字节
B22 数据 经度最低字节
B23 数据 经度低字节
B24 数据 经度高字节
B25 数据 经度最高字节
  1. 校验和 B17(8位无符号整数) 等于: B1+B2+······ B16 的值。

  2. 当温度的数据 B13 的 bit7=1 时, 代表是负温度, B13 的 bit7=0 时,是正温度。 例如, 当 B13=9Bh 时, 此时 bit7=1, 表示是负温度, 此时实际温度就是-27℃ ;如果 B13=1Bh,此时 bit7=0,代表是正温度, 此时实际温度就是 27℃ 。

  3. 温度和湿度的小数部分分辨率都是0.1,也就是说正常情况下,他们都是0到9的整数。

  4. 经纬度都是32位有符号整数(int32_t)。

  5. 当GNSS模块收到卫星信号的时候,获取到的传感器数据会保存在以当前日期命名的文本文件中(例如:2020-12-08.txt),文件中的内容如下:

    2020-12-08 00:00:32 623 17 124 137 170 14.5 48.0 32.161813 118.686683

    2020-12-08 00:00:34 623 17 124 138 171 14.5 48.0 32.161812 118.686683

    2020-12-08 00:00:36 623 17 124 138 171 14.5 48.1 32.161809 118.686682

    2020-12-08 00:00:38 623 17 124 139 173 14.5 48.1 32.161806 118.686681

    2020-12-08 00:00:40 625 17 125 140 174 14.5 48.2 32.161804 118.686679

    每一行从左到右的数据依次为:时间,二氧化碳浓度(ppm),甲醛浓度(ug/m³),TVOC浓度(ug/m³),PM2.5浓度(ug/m³), PM10浓度(ug/m³),温度(℃),湿度(%),纬度,经度。