目录

一、前言

很开心可以开始第一次博客之旅,从这篇文章开始我将会记录每次关于嵌入式学习的项目记录和日记,以此来不断加强自己的专业技术,首先非常感谢正点原子所有教程视频、开源资料带给我的帮助,不管是各种硬件板子、软件代码,都让我更加对嵌入式Linux着迷,我很多项目都是参考网上很多大佬开源项目进行学习,也让我走上了嵌入式学习之路!也希望以后能与其他电子爱好者更多的进行技术分享交流,若哪里做得不好不对的地方需要改进的,请大家多多指教!

这个项目是基于恩智浦芯片i.MX6ULL控制的嵌入式Linux系统,而且采用QT界面设计来进行显示控制,可以利用触摸屏对炫彩灯进行不同占空比的输出,根据HC05蓝牙模块与手机进行通信,手机也可以发送不同字符串来控制炫彩灯的各种灯效。至于嵌入式Linux的各种如Ubuntu安装、QT、Uboot、根文件系统、触摸屏等其他驱动移植这里暂时就不写了,这个文章主要专注于记录功能的实现,QT源代码也会进行开源分享,将会放在本文最下方!!!

二、PWM控制炫彩灯

1、设备树添加引脚和节点内容

这里使用厂家内核自带的PWM驱动,自己就不需要编写了,记得在Linux内核配置界面打开使能就好。炫彩灯为红蓝绿三种颜色,所以需要三个引脚进行PWM输出。只需要在设备树文件的iomuxc节点下添加引脚信息,然后再对这三个引脚各自添加三个节点设置时钟就可以了。

首先需要屏蔽掉其他复用的IO,避免重复使用,板子有许多PWM通道,这里使用PWM3通道对应复用引脚之一的GPIO_IO04引脚,PWM6通道对应复用引脚之一的ENET1_TX_EN,PWM8通道对应复用引脚之一的ENET1_RX_ER。具体通道还有哪些复用引脚等可以使用请参考芯片手册!

pinctrl_pwm3: pwm3grp {
 	fsl,pins = <
 		MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
 	>;
};

pinctrl_pwm_6: pwm6grp {
 	fsl,pins = <
 		MX6UL_PAD_ENET1_TX_EN__PWM6_OUT   0x10b0
 	>;
};
		
pinctrl_pwm_8: pwm8grp {
 	fsl,pins = <
 		MX6UL_PAD_ENET1_RX_ER__PWM8_OUT	0x10b0
 	>;
};

节点这里每个引脚需要对应使用的PWM通道并且设置时钟和okay状态,然后编译下设备树文件,成功后把文件放到板子上重新启动。

&pwm3 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pwm3>;
 	status = "okay";
};

&pwm6 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pwm_6>;
	  clocks = <&clks IMX6UL_CLK_PWM6>,
             <&clks IMX6UL_CLK_PWM6>;
    clock-names = "ipg", "per";
 	status = "okay";
};

&pwm8 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pwm_8>;
	  clocks = <&clks IMX6UL_CLK_PWM8>,
             <&clks IMX6UL_CLK_PWM8>;
    clock-names = "ipg", "per";
 	status = "okay";
};

2、PWM驱动测试

开发板启动完后进入终端根目录/sys/class/pwm/中,这里存放pwm驱动的所有通道,目录显示的pwmchip0 ~ 7分别对应实际的pwm1 ~ 6,这里先设置红灯的pwm6通道对应pwmchip5,利用sys接口方式进行pwm驱动。

首先输入指令“echo 0 >/sys/class/pwm/pwchip5/export”,export导出pwm0的接口,然后在对应的pwm通道目录pwmchip5中,找到pwm0进入就可以设置参数了。

设置参数测试有3个步骤,(1)先输入指令“echo 1 >/sys/class/pwm/pwmchip5/pwm0/enable”,enable使能可以驱动起来,想关闭就把1改为0灯就灭了。
(2)然后输入“echo 50000 >/sys/class/pwm/pwmchip5/pwm0/period”,设置周期50000。
(3)跟着输入“echo 10000 >/sys/class/pwm/pwmchip5/pwm0/duty_cycle”,占空比为10000,然后就可以看到灯是否亮起来了,如果不清晰可以不断增加占空比看看。另外2个pwm也是同理进行导出驱动。
在这里插入图片描述

3、错误总结

一开始错误示范:这里设置节点只有简单设置通道,并没有处理其他东西,导致输入echo 1 enable使能时整个终端卡住,灯没有任何反应。
在这里插入图片描述
正确做法:后面发现是时钟的问题,因为PWM计数器无时钟驱动 → 无法生成波形,内核访问未初始化的模块 → 触发超时阻塞(卡死),所以应该还要设置时钟:
在这里插入图片描述

三、开发板与电脑进行蓝牙串口通信

1、蓝牙模块HC05参数设置

用电脑串口助手XCOM进行设置,先按住HC05重置按键然后在上电,这样进入AT命令响应模式对模块进行一些参数设置了,LED灯由快闪变成双慢闪。这里列出需要用到的AT指令:

指令名 响应回复 含义
AT OK 测试指令
AT+RESET OK 模块复位
AT+UART=115200,0,0 OK 设置串口参数
AT+UART? +UART:115200,0,0 OK 显示串口参数

大部分名字密码可以默认(可以自己修改),默认使用的波特率为38400,需要修改下,这里设置关键的串口参数115200,0,0,第一个为对应匹配波特率115200,第二个为停止位0(0指1),第三个校验位0为None。
在这里插入图片描述

2、手机蓝牙通过HC05与电脑串口通信测试

修改好参数就可以重新上电跟手机配对了,选择正确串口和波特率115200,打开串口按下发送按键发送信息“我是电脑!”。
在这里插入图片描述
首先蓝牙需要配对HC05成功后,然后再使用蓝牙串口助手APP,按下连接成功后发送“我是手机!”,双端成功实现通信!!
在这里插入图片描述

3、开发板通过HC05与电脑串口通信测试

这里先移植minicom串口助手到开发板中(电脑软件MobaXterm终端显示),输入指令-v查看版本号成功则移植成功,输入指令-s运行minicom测试。
在这里插入图片描述
进来后先进入“Serial port setup”进行串口设置。
在这里插入图片描述
这个为串口设置界面,按键盘“A”设置串口设备为ttymxc2(对应开发板串口接口名称),按“E”设置比特为115200对应蓝牙模块波特率,按“F”设置“”Hardware Flow Control”设置为No,其他默认就好,然后按enter确认退出。
在这里插入图片描述
然后按下“Ctrl-A Z”进入信息设置界面后,按下键盘字母“E”打开回显功能,这样接收的信息才会显示在终端上面。开发板(电脑终端界面显示)通过蓝牙串口与手机进行通信成功(终端直接输入就可以直接发送过去了):
在这里插入图片描述
手机蓝牙APP界面成功显示发送“enen”和接收到“233”(但是有几率出现空格“ ”不知道为什么,有时还会出现问号乱码):
在这里插入图片描述

4、错误总结

无论是手机端还是开发板端若出现乱码,可能是字符集不匹配造成的,需要对应的字符集一样才可以,因为后面QT代码用的串口发送接收数据都是以UTF-8的字符集,所以手机APP设置同样的UTF-8,若还是出现乱码可以设置同样的GBK字符集试试。
在这里插入图片描述

四、QT界面功能总设计与测试

1、炫彩灯控制

炫彩灯有红绿蓝三种颜色,它们各种占空比输出的亮度大小可以实现不同种颜色,各自设计三个滑条QT控件类"Horizontal Scroll Bar",滑条数值最大,占空比越大,亮度越大,一种颜色最大值为255,可以设计三种颜色不同比例来组合颜色,如红255,绿255,蓝为255,全设置最大值时,炫彩灯显现纯白色。
在这里插入图片描述
ui布局放好之后,创建三个滑条槽函数,当滑动滑条时进入槽函数,文本框rednum显示数值,传送滑动值到redctrl函数(此为设置占空比函数),另外两种颜色函数同样设计。

//红灯占空比、滑条和显示框使用函数
void Widget::on_red_valueChanged(int value)
{
    ui->red->setValue(value);
    ui->rednum->setText(QString::number(value));
    redctrl(value);
}

//绿灯滑条和显示框使用函数
void Widget::on_green_valueChanged(int value)
{
    ui->green->setValue(value);
    ui->greennum->setText(QString::number(value));
    greenctrl(value);
}

//蓝灯滑条和显示框使用函数
void Widget::on_blue_valueChanged(int value)
{
    ui->blue->setValue(value);
    ui->bluenum->setText(QString::number(value));
    bluectrl(value);

}

这里用sys接口的方式去控制PWM,所以先需要导出接口,这段代码直接放到最前面默认运行程序就运行:

    QString cmd_config1 = "echo 0 > /sys/class/pwm/pwmchip2/export";
    QByteArray cmdby_off1 = cmd_config1.toLatin1();
    char* charCmd_config1 = cmdby_off1.data();
    system(charCmd_config1);

    QString cmd_config2 = "echo 0 > /sys/class/pwm/pwmchip5/export";
    QByteArray cmdby_off2 = cmd_config2.toLatin1();
    char* charCmd_config2 = cmdby_off2.data();
    system(charCmd_config2);

    QString cmd_config3 = "echo 0 > /sys/class/pwm/pwmchip7/export";
    QByteArray cmdby_off3 = cmd_config3.toLatin1();
    char* charCmd_config3 = cmdby_off3.data();
    system(charCmd_config3);

当数值进入改变控制亮度大小的函数(改变占空比)时,因为占空比最大只有100%,所以数值(滑条最大数值为255)先进行转换对应比例,然后设置频率、占空比、使能。

//红色占空比改变函数
void Widget::redctrl(int duty)
{
    /*QString path = PWM_LED_PATH; */
    /* 由于system函数只能用char类型,所以进行了转换 */
    /* config */
    /* period */
    QString cmd_period = "echo 1000000 > /sys/class/pwm/pwmchip5/pwm0/period";
    QByteArray cmdby_period = cmd_period.toLatin1();
    char* charCmd_period = cmdby_period.data();
    system(charCmd_period);

    int change = duty;
    change = (change*1000000)/255;
    qDebug() << "红色: " << change;

    /* duty cycle */
    QString cmd_duty = "echo "+ QString::number(change, 10) +" > /sys/class/pwm/pwmchip5/pwm0/duty_cycle";
    QByteArray cmdby_duty = cmd_duty.toLatin1();
    char* charCmd_duty = cmdby_duty.data();
    system(charCmd_duty);

    /* enable */
    QString cmd_enable = "echo 1 > /sys/class/pwm/pwmchip5/pwm0/enable";
    QByteArray cmdby_enable = cmd_enable.toLatin1();
    char* charCmd_enable = cmdby_enable.data();
    system(charCmd_enable);
}

//绿色占空比改变函数
void Widget::greenctrl(int duty)
{
    /*QString path = PWM_LED_PATH; */
    /* 由于system函数只能用char类型,所以进行了转换 */
    /* config */
    /* period */
    QString cmd_period = "echo 1000000 > /sys/class/pwm/pwmchip7/pwm0/period";
    QByteArray cmdby_period = cmd_period.toLatin1();
    char* charCmd_period = cmdby_period.data();
    system(charCmd_period);

    int change = duty;
    change = (change*1000000)/255;
    qDebug() << "绿色: " << change;

    /* duty cycle */
    QString cmd_duty = "echo "+ QString::number(change, 10) +" > /sys/class/pwm/pwmchip7/pwm0/duty_cycle";
    QByteArray cmdby_duty = cmd_duty.toLatin1();
    char* charCmd_duty = cmdby_duty.data();
    system(charCmd_duty);

    /* enable */
    QString cmd_enable = "echo 1 > /sys/class/pwm/pwmchip7/pwm0/enable";
    QByteArray cmdby_enable = cmd_enable.toLatin1();
    char* charCmd_enable = cmdby_enable.data();
    system(charCmd_enable);
}

//蓝色占空比改变函数
void Widget::bluectrl(int duty)
{
    /*QString path = PWM_LED_PATH; */
    /* 由于system函数只能用char类型,所以进行了转换 */
    /* config */
    /* period */
    QString cmd_period = "echo 1000000 > /sys/class/pwm/pwmchip2/pwm0/period";
    QByteArray cmdby_period = cmd_period.toLatin1();
    char* charCmd_period = cmdby_period.data();
    system(charCmd_period);

    int change = duty;
    change = (change*1000000)/255;
    qDebug() << "蓝色: " << change;

    /* duty cycle */
    QString cmd_duty = "echo "+ QString::number(change, 10) +" > /sys/class/pwm/pwmchip2/pwm0/duty_cycle";
    QByteArray cmdby_duty = cmd_duty.toLatin1();
    char* charCmd_duty = cmdby_duty.data();
    system(charCmd_duty);

    /* enable */
    QString cmd_enable = "echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable";
    QByteArray cmdby_enable = cmd_enable.toLatin1();
    char* charCmd_enable = cmdby_enable.data();
    system(charCmd_enable);
}

2、串口通信

串口界面需要下拉控件ComboBox,文本框TextBrowser,按键PushButton,发送文本框TextEdit并且双击设置预发送的字符串(本来需要写个虚拟键盘的,但是工作量有亿点点大,这里偷懒嘿嘿就发送这句话算了),按照如图布局:
在这里插入图片描述
双击下拉框进行设置波特率:
在这里插入图片描述
双击下拉框进行设置停止位:
在这里插入图片描述
双击下拉框进行设置数据位:
在这里插入图片描述

双击下拉框进行设置校验位:
在这里插入图片描述
按下“打开串口”这个按键转到槽函数(bool类型),当检测到按键被按下的时候,串口类serialPort就匹配设置串口需要的一些配置名字、波特率、停止位、数据位,但是校验位的枚举不是数值,都是为字符串所以用switch来设置,然后继续设置流控,再加个若打开串口失败的弹窗。

//打开串口按键函数
void Widget::on_pushButton_clicked(bool checked)
{
    if (checked) {
        //设置要打开的串口名字
        serialPort->setPortName(ui->comboBox->currentText());

        //设置波特率
        serialPort->setBaudRate(ui->comboBox_2->currentText().toInt());

        //设置停止位
        serialPort->setStopBits(QSerialPort::StopBits(ui->comboBox_3->currentText().toInt()));

        //设置数据位
        serialPort->setDataBits(QSerialPort::DataBits(ui->comboBox_4->currentText().toInt()));

        //设置校验位,枚举不是数值,是字符串所以用switch
        switch (ui->comboBox_5->currentIndex()) {
        case 0:
            serialPort->setParity(QSerialPort::NoParity);
            break;
        case 1:
            serialPort->setParity(QSerialPort::EvenParity);
            break;
        case 2:
            serialPort->setParity(QSerialPort::OddParity);
            break;
        case 3:
            serialPort->setParity(QSerialPort::SpaceParity);
            break;
        case 4:
            serialPort->setParity(QSerialPort::MarkParity);
            break;
        default:
            break;

        }

        //设置流控
        serialPort->setFlowControl(QSerialPort::NoFlowControl);

        if (!serialPort->open(QIODevice::ReadWrite)) {
           QMessageBox::about(this, "错误", "串口打开失败可能被占用");//弹出弹窗表示提醒
           return;
        }

        //打开串口时按键不可用
        ui->comboBox->setEnabled(false);
        ui->comboBox_2->setEnabled(false);
        ui->comboBox_3->setEnabled(false);
        ui->comboBox_4->setEnabled(false);
        ui->comboBox_5->setEnabled(false);

        //串口操作按键显示“关闭串口”
        ui->pushButton->setText("关闭串口");

    }else {
        //关闭串口
        serialPort->close();

        //关闭串口后按键可继续选用
        ui->comboBox->setEnabled(true);
        ui->comboBox_2->setEnabled(true);
        ui->comboBox_3->setEnabled(true);
        ui->comboBox_4->setEnabled(true);
        ui->comboBox_5->setEnabled(true);

        //串口操作按键显示“打开串口”
        ui->pushButton->setText("打开串口");
    }

}

扫描本机串口这段代码直接放前面,当程序一运行时就直接开始扫描串口设备,按下“打开串口”按键成功匹配后,再把串口设备名称显示到下拉框中:

    serialPort = new QSerialPort(this);

    //扫描本机的串口,并且添加到下拉框里
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ui->comboBox->addItem(info.portName());
    }

    //读取数据
    connect(serialPort, SIGNAL(readyRead()), this, SLOT(readData()));

按下“发送”按键进入槽函数,把文本框的预输入的字符串用匹配的Utf8类型发送,清空文本框字符串直接就clear完事了。

//发送数据函数
void Widget::on_pushButton_2_clicked()
{
    //发送数据
    serialPort->write(ui->textEdit->toPlainText().toUtf8());
   /*
    QString text = ui->textEdit->toPlainText();

    QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");
    if (gbkCodec) {
        QByteArray gbkData = gbkCodec->fromUnicode(text);
        serialPort->write(gbkData);
    }*/
}

//清空发送数据
void Widget::on_pushButton_3_clicked()
{

    ui->textEdit->clear();
}

当接收到数据时,把数据不断append追加到文本框中,这样就不会实时更新,数据会留有记录,再根据手机发送的字符串(1,2,3简简单单)匹配来控制灯效。

//接收数据函数
void Widget::readData()
{
    //接收数据显示到文本框中
    QByteArray change = serialPort->readAll();//只调用一次,就清空缓冲区数据

    //显示数据在文本框中,append为追加,所有数据存留在文本框中
    ui->textBrowser->append(change);

    QString shuju = QString(change).trimmed();

    qDebug() << "数据为:" << shuju;

    //蓝牙接收字符串调灯
    if (shuju == "1"){
        on_pushButton_4_clicked(true);//收到指令进入LED按键槽函数
        qDebug() << "接收到1了啊!!" ;
    }

    if (shuju == "2"){
        on_pushButton_5_clicked(true);
        qDebug() << "接收到2了啊!!" ;
    }

    if (shuju == "3"){
        on_pushButton_6_clicked(true);
        qDebug() << "接收到3了啊!!" ;
    }

}

3、蓝牙控制灯效

最终界面如图:
在这里插入图片描述
另外在右下角添加了3种灯效,直接按下按键炫彩灯就可以变成一种灯效,当蓝牙接收到不同字符串时,将会进入对应的灯效函数,里面的灯效函数传送已设定好的数值改变占空比。

//霓虹灯灯效
void Widget::on_pushButton_4_clicked(bool checked)
{
    if (checked){
        /*
        redctrl(255);
        greenctrl(0);
        bluectrl(255);
        */
        this->on_red_valueChanged(255);
        this->on_green_valueChanged(0);
        this->on_blue_valueChanged(255);
        ui->pushButton_4->setChecked(true);//确保按键为已按下状态
        ui->textBrowser->append("正在显示霓虹灯灯效!");
    }

}

//海洋灯效
void Widget::on_pushButton_5_clicked(bool checked)
{
    if (checked){
        /*
        redctrl(0);
        greenctrl(255);
        bluectrl(255);
        */
        this->on_red_valueChanged(0);
        this->on_green_valueChanged(255);
        this->on_blue_valueChanged(255);
        ui->pushButton_5->setChecked(true);
        ui->textBrowser->append("正在显示海洋灯效!");
    }
}

//深紫主题灯效
void Widget::on_pushButton_6_clicked(bool checked)
{
    if (checked){
        /*
        redctrl(204);
        greenctrl(0);
        bluectrl(204);
        */
        this->on_red_valueChanged(204);
        this->on_green_valueChanged(0);
        this->on_blue_valueChanged(204);
        ui->pushButton_6->setChecked(true);
        ui->textBrowser->append("正在显示深紫主题灯效!");
    }
}

4、错误总结

在测试蓝牙控制炫彩灯时,手机发送字符串后,终端和文本框都毫无反应,什么都不显示,而且灯也没有变化。

一开始的错误示范:然后发现这里serialPort->readAll()是属于调用一次数据后就完全清除缓冲区的数据了,第二次再调用相当于储存了个寂寞,本来想先显示文本框后再储存数据到change中这个方法是不对的。
在这里插入图片描述
正确做法:所以将这3行改为先调用一次直接储存数据到change中(这样只用一次serialPort->readAll()了),后利用change再显示文本框,转换数据为QString类型判断发来的字符串等操作。

//接收数据显示到文本框中
QByteArray change = serialPort->readAll();//只调用一次,就清空缓冲区数据

//显示数据在文本框中,append为追加,所有数据存留在文本框中
ui->textBrowser->append(change);

QString shuju = QString(change).trimmed();

五、总结与演示

QT代码构建编译好把可执行文件下载到开发板上,然后在MobaXterm终端上执行。1和3的灯效占空比数值相差不大,可能肉眼看不出来效果,重点是可以控制灯!!所以懒得改了,想弄其他颜色可以单独对灯效按键槽函数改变占空比数值(也是滑块的数值)!!!

功能演示如下:

蓝牙控制炫彩灯演示视频

这个小项目很早就搞定了,虽然很简单,但是是这里的第一次开始,写文章的时间感觉比做项目还要久哎。。希望以后能继续成长,继续学习更多电子技术,与更多大佬爱好者交流分享更多好项目好文章!!!

QT源代码和手机电脑相关APP都放git上面了,链接: link

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐