微信小程序> 离子色谱仪实验[嵌入式Linux项目]-Qt开发日志

离子色谱仪实验[嵌入式Linux项目]-Qt开发日志

浏览量:3097 时间: 来源:Keyon7

环境搭建

先说一下搭建以下环境的原因,开发流程如图。

  1. Win10下开发Qt程序,需要Qt5与Qt Creator
  2. 虚拟机下安装Ubuntu16(高版本如18不行),安装Qt5交叉编译链
  3. 将编译后的ARM环境下的可执行程序通过FTP或SFTP上传至嵌入式Linux生产环境,运行可执行程序。

小程序

ARM生产环境

实验设备为 FriendlyARM 的 CortexA9 Smart4418 实验箱。配套资源网址 为:http://wiki.friendlyarm.com/wiki/index.php/Smart4418/zh

OS:FriendlyCore,是一个没有 X-windows 环境,基于 Ubuntucore 构建的系统,使用 Qt-Embedded 作为图形界面的轻量级系统,兼容 Ubuntu 系统软件源,非常适合于企业用户用作产品的基础 OS。

PC开发环境

开发在虚拟机下的Ubuntu16系统,安装了Qt5、Qt Creator以及Qt5交叉编译链工具。

Qt5安装

sudo apt-get install qt5

Qt Creator安装

Windows上安装Qt Creator,编写运行成功后,传至Linux上进行交叉编译,将目标程序放ARM上直接与运行。

Qt5交叉编译链

FriendlyARM交叉编译链arm-linux-gcc下载地址:https://github.com/friendlyarm/prebuilts

如何交叉编译(以编译QtE-Demo项目为例):

mkdir build && cd build/usr/local/Trolltech/Qt-5.10.0-nexell32-sdk/bin/qmake ../QtE-Demo.promake

在ARM机上运行,如提示:EGL library doesn't support Emulator extensions

输入如下命令解决:

export QT_QPA_EGLFS_INTEGRATION=none

ARM网络设置

ARM连接电脑热点

查看Wifi设备

nmcli dev

开启Wifi设备

nmcli r wifi on

扫描附近Wifi热点

nmcli dev wifi

连接到指定Wifi热点

nmcli dev wifi connect "SSID" password "PASSWORD" ifname wlan0

查看IP地址

ifconfig

第一次连接后,以后每次开机,ARM都会自动连接之前的Wifi。

通过Win10网络设置也可以看到已连接设备的IP。

小程序

SSH远程访问ARM

拿到设备IP后,使用SSH协议远程访问ARM,使用SFTP协议上传下载程序。

工具:MobaXterm

小程序

小程序

ARM串口读写

GPIO引脚控制LED

linux下对/sys/class/gpio中的gpio的控制 (转)

  1. 配置GPIO
  2. 设置GPIO的方向
  3. 设置GPIO的输出电平

配置GPIO43与GPIO44,其分别控制两个LED灯的亮灭。

cd /sys/class/gpioecho 43 > exportecho 44 > exportcd gpio43echo high > directionecho low > directionecho 1 > valueecho 0 > valuecd gpio44...

测试Demo:

//初始化引脚int fd;char buf[5];char gpioExportPath[30] = "/sys/class/gpio/export";//配置GPIOfd = open(gpioExportPath, O_WRONLY);if (fd < 0) {    printf("gpioExportPath:%stopen failed", gpioExportPath);    exit(1);}sprintf(buf, "%2d",this->ledNo);write(fd, buf, 2);close(fd);//设置GPIO方向char gpioDirPath[40];sprintf(gpioDirPath, "/sys/class/gpio/gpio%d/direction", this->ledNo);fd = open(gpioDirPath, O_WRONLY);if (fd < 0) {    printf("gpioDirPath:%stopen failed", gpioDirPath);    exit(1);}sprintf(buf, "high");write(fd, buf, 3);close(fd);
//控制亮灭void updateLedStatus(int ledNo){    int fd;    char buf[2];    char path[30];    sprintf(path, "/sys/class/gpio/gpio%d/value" , ledNo);    fd = open(path, O_WRONLY);    if (fd < 0) {        printf("updateLedStatus failed");        exit(1);    }    if (ledStatus)    {        buf[0] = '1';    }    else    {        buf[0] = '0';    }    write(fd, buf, 1);    close(fd);}

AD读取

测试Demo

#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>void delay(){int i,j;for (i = 0; i < 200; i++)for (j = 0; j < 100; j++);}int main(){int fd, len, i;char buf[20];for (i = 0; i < 5; i++){fd = open("/sys/devices/platform/c0000000.soc/c0053000.adc/iio:device0/in_voltage7_raw", 0);if (fd < 0) exit(1);len = read(fd, buf, sizeof buf - 1);if (len > 0){buf[len] = '';printf("%s", buf);}delay();}close(fd);return 0;}

蜂鸣器

http://wiki.friendlyarm.com/wiki/index.php/Matrix_-_Buzzer/zh

https://www.eefocus.com/toradex/blog/17-05/420816_04520.html

cd /sys/class/pwm/pwmchip0echo 0 > exportcd pwm0echo 400000000 > period  #周期,单位nsecho 100000000 > duty_cycle #设置占空比25%echo 1 > enable  #开启echo 0 > enable  #关闭

需求建模

用例图

小程序

小程序

活动图

小程序

程序编码

服务端打算用Qt编写,开发流程是:1. 在Win10上使用IDE开发 2. 在虚拟机Linux上交叉编译  3. 在嵌入式Linux上运行程序查看效果。

界面设计

根据详细设计的功能,规划界面内容。

界面上展示的包括:LED控制、色谱图模块、LCD数值显示模块、网络连接展示模块。

后端隐含的功能模块包括:网络连接模块、网络传输模块、网络数据解析模块、GPIO读写模块、AD读取模块、蜂鸣器控制模块。

小程序

小程序

LED控制模块

先来实现LED控制功能。

设计一个LED控制类。

小程序

ledcontrol.h

#ifndef LEDCONTROL_H#define LEDCONTROL_H/** * @brief LED控制类 */class LEDControl{public:    LEDControl(int no);    void initGPIO(); //初始化GPIO引脚    void setLedStatus(bool status); //设置LED状态private:    void updateLedStatus(); //更新GPIO引脚状态,控制LED亮灭public:    int ledNo; //LED编号    bool ledStatus; //LED状态};#endif // LEDCONTROL_H

ledcontrol.cpp

#include "ledcontrol.h"#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <QDebug>/** * @brief LED控制类 */LEDControl::LEDControl(int no){    this->ledNo = no;    initGPIO(); //初始化LED对应的GPIO引脚    this->setLedStatus(true); //默认高电平,灭}/** * @brief 初始化LED对应的GPIO引脚 */void LEDControl::initGPIO(){    int fd;    char buf[5];    char gpioExportPath[30] = "/sys/class/gpio/export";    //配置GPIO    fd = open(gpioExportPath, O_WRONLY);    if (fd < 0) {        printf("gpioExportPath:%stopen failed", gpioExportPath);        exit(1);    }    sprintf(buf, "%2d",this->ledNo);    write(fd, buf, 2);    close(fd);    //设置GPIO方向    char gpioDirPath[40];    sprintf(gpioDirPath, "/sys/class/gpio/gpio%d/direction", this->ledNo);    fd = open(gpioDirPath, O_WRONLY);    if (fd < 0) {        printf("gpioDirPath:%stopen failed", gpioDirPath);        exit(1);    }    sprintf(buf, "high");    write(fd, buf, 3);    close(fd);}/** * @brief 设置LED状态 false亮, true灭 */void LEDControl::setLedStatus(bool status){    this->ledStatus = status;    updateLedStatus();    qDebug() << "led" << this->ledNo << "t status:" << this->ledStatus;}/** * @brief 更新LED状态 */void LEDControl::updateLedStatus(){    int fd;    char buf[2];    char path[30];    sprintf(path, "/sys/class/gpio/gpio%d/value" , this->ledNo);    fd = open(path, O_WRONLY);    if (fd < 0) {        printf("updateLedStatus failed");        exit(1);    }    if (this->ledStatus)    {        buf[0] = '1';    }    else    {        buf[0] = '0';    }    write(fd, buf, 1);    close(fd);}

AD读取模块

小程序

adreader.h

#ifndef ADREADER_H#define ADREADER_H/** * @brief AD读取类 * 读取电压模拟量 */class ADReader{public:    ADReader();    int readVol(); //电压模拟量读取private:    char buf[20]; //AD读取缓冲区    int readLen; //当次读取数据长度    int adFileDesc; //AD设备文件描述符};#endif // ADREADER_H

adreader.cpp

#include "adreader.h"#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <unistd.h>/** * @brief AD读取类 * 读取电压模拟量 */ADReader::ADReader(){}/** * @brief 读取电压模拟量 * @return */int ADReader::readVol(){    //Win环境下测试用    //if (true)    //{    //   return 800;    //}    //打开AD设备文件    this->adFileDesc = open("/sys/devices/platform/c0000000.soc/c0053000.adc/iio:device0/in_voltage7_raw", 0);    if (this->adFileDesc < 0)    {        printf("adFile open failed");        exit(1);    }    //读取电压模拟量到buf    this->readLen = read(this->adFileDesc, this->buf, sizeof this->buf - 1);    if (this->readLen > 0)    {        this->buf[this->readLen] = '';    }    close(this->adFileDesc);    return atoi(buf); //转化为int返回}

蜂鸣器控制模块

对于服务端,如果读取的AD值超出安全范围,则控制蜂鸣器发出警报,同时告诉客户端警报开启。

buzzer.h

#ifndef BUZZER_H#define BUZZER_H/** * @brief 蜂鸣器 */class Buzzer{public:    Buzzer();    void setEnable(int enable_);private:    void initPWM();    void updateEnable();    int enable; //1开启,0关闭};#endif // BUZZER_H

buzzer.cpp

#include "buzzer.h"#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <QDebug>/** * @brief 蜂鸣器 */Buzzer::Buzzer(){    initPWM();}/** * @brief 初始化PWM */void Buzzer::initPWM(){    int fd;    char buf[15];    char exportPath[35] = "/sys/class/pwm/pwmchip0/export";    //配置PWM    fd = open(exportPath, O_WRONLY);    if (fd < 0) {        printf("pwmExportPath:%stopen failed", exportPath);        exit(1);    }    buf[0] = '0';    write(fd, buf, 1);    close(fd);    //设置PWM周期    char periodPath[45] = "/sys/class/pwm/pwmchip0/pwm0/period";    fd = open(periodPath, O_WRONLY);    if (fd < 0) {        printf("pwmPeriodPath:%stopen failed", periodPath);        exit(1);    }    sprintf(buf, "400000000");    write(fd, buf, 9);    close(fd);    //设置PWM占空比    char dutyCyclePath[55] = "/sys/class/pwm/pwmchip0/pwm0/duty_cycle";    fd = open(dutyCyclePath, O_WRONLY);    if (fd < 0) {        printf("pwmDutyCyclePath:%stopen failed", dutyCyclePath);        exit(1);    }    sprintf(buf, "100000000");    write(fd, buf, 9);    close(fd);}/** * @brief 更新蜂鸣器状态 */void Buzzer::updateEnable(){    int fd;    char buf[2];    char path[45] = "/sys/class/pwm/pwmchip0/pwm0/enable";    fd = open(path, O_WRONLY);    if (fd < 0) {        printf("updatePWMEnable failed");        exit(1);    }    buf[0] = '0' + this->enable;    write(fd, buf, 1);    close(fd);}/** * @brief 设置蜂鸣器开关状态 * @param enable */void Buzzer::setEnable(int enable_){    if(this->enable == enable_) return; //防止重复写    this->enable = enable_;    updateEnable();    qDebug() << "PWM: " << this->enable;}

网络模块

基于TCPSocket进行网络通讯。

设计流程:

  1. 实现TCP服务端
  2. 使用TCP网络调试助手,测试服务端发送接收消息功能无误
  3. 制定通讯协议
  4. 实现TCP客户端

服务端网络模块

使用QT封装的TCP网络通讯类:QTcpServer与QTcpSocket。

使用Qt建立一个TCP服务端分为以下几步:

  • 设置监听端口
  • 绑定newConnection信号与处理该信号的槽函数

Sample:

//设置监听端口tcpserver->listen(QHostAddress::Any, this->serverPort.toInt());//新客户端连接connect(tcpserver, &QTcpServer::newConnection, this, &MainWidget::newConnect);

在newConnect槽函数处理TCP客户端的连接请求。

处理流程:

  1. 获取客户端Socket
  2. 绑定readyRead信号与处理该信号的槽函数
  3. 绑定disconnected信号与处理该信号的槽函数
  4. 获取当前客户端的IP与端口,界面展示用
  5. 将当前客户端Socket添加到List统一管理
/** * @brief 新客户端连接 */void MainWidget::newConnect(){    clientSocket = tcpserver->nextPendingConnection();    qDebug() << "新客户端连接:"             << QHostAddress(clientSocket->peerAddress().toIPv4Address()).toString();    //有可读消息    connect(clientSocket,SIGNAL(readyRead()),            this,SLOT(readMessage()));    //断开连接    connect(clientSocket,SIGNAL(disconnected()),            this,SLOT(lostConnect()));    //地址    QString currentIP = QHostAddress(clientSocket->peerAddress().toIPv4Address()).toString();    quint16 currentPort = clientSocket->peerPort();    //添加到显示面板    client_list->push_back(currentIP + ":" + QString::number(currentPort));    ui->tb_clients->clear();    for (int i = 0; i < client_list->length(); i++)    {        ui->tb_clients->append(client_list->at(i));    }    //添加到列表    socket_list->push_back(clientSocket);}/** * @brief 接收消息 */void MainWidget::readMessage(){    //遍历客户端列表,所有客户端    for (int i = 0; i < socket_list->length(); i++)    {        read_message = socket_list->at(i)->readAll();        if(!(read_message.isEmpty()))        {            qDebug() << "读取消息 [ClientIP:" << QHostAddress(socket_list->at(i)->peerAddress().toIPv4Address()).toString() << "]";            qDebug() << "[ClientMsg]:" <<  read_message;            //回传同样的消息            sendMessage(read_message);            //解析消息            analyzeMessage(read_message, socket_list->at(i));            break;        }    }}/** * @brief 发送消息 * @param infomation */void MainWidget::sendMessage(QString infomation){    QByteArray b = infomation.toLatin1();    char* msg = b.data();    for(int i = 0; i < socket_list->length(); i++){        socket_list->at(i)->write(msg);        qDebug() << "Send:"<< msg;    }}

测试TCP服务端

确定接收与发送功能无误。

小程序

通讯协议制定

制定TCP服务端与客户端之间的通讯协议。

数据首部:#

  1. 客户端要读取AD通道0的采样值
    1. 发送 #R1  开启读取
    2. 发送 #R0  关闭读取
  2. 服务端返回采样值
    1. 发送 #B00800,表示800mV,有效数据长度为5。
  3. 客户端要控制LED的亮灭,或者服务端通知客户端当前更新后的LED状态
    1. 发送 #L11  两个LED都灭
    2. 发送 #L00  两个LED都亮
    3. 发送 #L01  LED1灭,LED2亮
    4. 发送 #L10  LED1亮,LED2灭
  4. 服务端返回报警信号:
    1. 发送 #W1  报警提示

客户端网络模块

客户端创建一个TCP连接的流程:

  1. 初始化操作,就是绑定一下需要处理的信号。
    1. 绑定readRead信号与处理该信号的槽函数
  2. 连接建立
    1. 调用connectToHost,指定服务端IP与端口,建立TCP连接
    2. 调用waitForConnected等待连接成功
  3. 连接断开
    1. 调用disconnectFromHost,断开与服务端的连接

Sample:

//init TCP Serverthis->tcpClient = new QTcpSocket(this);   //实例化tcpClientthis->tcpClient->abort();                 //取消原有连接connect(tcpClient, SIGNAL(readyRead()), this, SLOT(ReadData()));
/** * @brief 连接按钮点击事件 */void MainWidget::on_btn_conn_clicked(){    if(ui->btn_conn->text()=="连接")    {        tcpClient->connectToHost(ui->edit_ip->text(), ui->edit_port->text().toInt());        if (tcpClient->waitForConnected(1000))  // 连接成功则进入if{}        {            ui->btn_conn->setText("断开");            ui->btn_send->setEnabled(true);            ui->btn_led1->setEnabled(true);            ui->btn_led2->setEnabled(true);        }    }    else    {        tcpClient->disconnectFromHost();        if (tcpClient->state() == QAbstractSocket::UnconnectedState                 || tcpClient->waitForDisconnected(1000))  //已断开连接则进入if{}        {            ui->btn_conn->setText("连接");            ui->btn_send->setEnabled(false);        }    }}

消息解析模块

消息解析流程:

  1. 将接收到的msg根据#分割(可能一次发多条消息?一次只会发送接收一条消息的话,不加消息头不分割也是可以的)。
  2. 判断消息类型,获取消息携带的数据,执行不同的功能。

离子色谱图绘制模块

QCustomPlot:https://www.qcustomplot.com/

使用的QCustomPlot绘图窗口部件。

优点是没有过多的依赖库,使用方便,将头文件与源文件添加至项目工程即可。

 //init PlotSendTimeInterval = 500; //发送时间间隔//开始绘制setupRealtimeDataDemo(ui->plot);ui->plot->replot();

初始化Plot

流程:

  1. 添加一条数据线,设置颜色与数据名。
  2. 配置横坐标,数据类型、数据格式、间隔等。
  3. 纵坐标默认配置即可。
  4. 定义一个QTimer定时器,绑定timeout信号与处理该信号的槽函数。
  5. 在槽函数中获取数据并刷新Plot。
/** * @brief QCustomPlot初始化 * @param customPlot */void MainWidget::setupRealtimeDataDemo(QCustomPlot *customPlot){  customPlot->addGraph();  customPlot->graph(0)->setPen(QPen(Qt::red));  customPlot->graph(0)->setName("Vol");  //横坐标  customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);  customPlot->xAxis->setDateTimeFormat("hh:mm:ss");  customPlot->xAxis->setAutoTickStep(false);  customPlot->xAxis->setTickStep(2);  customPlot->axisRect()->setupFullAxesBox();  customPlot->legend->setVisible(true);//右上角小图标    //通过QTimer定时获取数据并刷新Plot  connect(&dataTimer, SIGNAL(timeout()), this, SLOT(realtimeDataSlot()));  dataTimer.start(SendTimeInterval);}
/** * @brief 绘制折线 */void MainWidget::realtimeDataSlot(){    //横轴:key 时间 单位s    double key = QDateTime::currentDateTime().toMSecsSinceEpoch()/1000.0;        //纵轴:value 电压模拟量    int value = this->adReader->readVol();    ui->lcd_vol->setDigitCount(4);    ui->lcd_vol->setMode(QLCDNumber::Dec);    ui->lcd_vol->display(QString::number(value));    //发送给客户端    if (adSendSwitch)    {        QString msg = QString("#B%1").arg(value,5,10,QLatin1Char('0'));        sendMessage(msg);    }    //添加数据到曲线0    ui->plot->graph(0)->addData(key, value);    //删除8秒之前的数据    ui->plot->graph(0)->removeDataBefore(key-8);    //设定graph曲线y轴的范围    ui->plot->yAxis->setRange(500, 7000);    ui->plot->xAxis->setRange(key+0.25, 8, Qt::AlignRight);//设定x轴的范围    ui->plot->replot();}

远程报警模块

对于服务端,如果读取的AD值超出安全范围,则控制蜂鸣器发出警报,同时告诉客户端警报开启。

客户端实现报警就是播放一段音频。

在QT项目中添加一个资源文件,存放warning.wav报警音频文件。

小程序

在项目pro文件,添加multimedia多媒体配置

版权声明

即速应用倡导尊重与保护知识产权。如发现本站文章存在版权问题,烦请提供版权疑问、身份证明、版权证明、联系方式等发邮件至197452366@qq.com ,我们将及时处理。本站文章仅作分享交流用途,作者观点不等同于即速应用观点。用户与作者的任何交易与本站无关,请知悉。

产品经理

手机 : 13312967497

擅长 : 小程序流量变现

扫码领取礼包

最新资讯

热门模板

  • 头条
  • 搜狐
  • 微博
  • 百家
  • 一点资讯
  • 知乎