概述
本课程设计是我们专业的无线通信原理的课程设计,当看到课题是关于无线通信的多点温湿度采集系统,并且还是使用51单片机的时候我的内心想到的既然是都大三了才让我们整51单片机。不过通过两周的课程设计我发现自己被打脸了,尽管是51单片机我也不能小觑它。
其实大部分同学看到这个课程设计的第一反应是“天哪,怎么这么难”,要写代码还要焊接电路。对于大一就接触51单片机的我来说难点就在于芯片的焊接(因为好久没有用烙铁了)和上位机显示相关的信息。不过幸运的是之前电赛的时候有学习了解过Pyqt的GUI开发。
废话不多说,先上成果!成果展示视频
文章目录
- 项目概述
- 硬件电路设计
- STC89C52控制电路
- 温湿度传感器电路
- LCD1602显示电路
- NRF24L01无线传输电路
- 硬件电路测试
- 下位机软件程序设计
- 温湿度数据的读取
- NRF24L01发送信息
- NRF24L01接收数据
- 上位机软件设计
- 实时从串口更新数据
- 绘制历史数据
- 总结
项目概述
随着现代信息社会的发展,通过无线通信来采集温湿度已经越来越普遍了。某些情况下监测范围大,布线不方便且不利于后期维护,这时我们就采用无线模块对温湿度进行采集。
系统利用两块PCB板的温湿度传感器DHT11芯片进行采集温湿度数据,再将收集到的数据传给核心芯片STC89C52处理,并将处理完的数据利用无线传输模块NRF24L01发射出去,同时通过数字显示屏LCD1602将处理后的数据显示出来。数据由另第三块PCB板的NRF14L01芯片接收。接收终端的NRF24L01芯片再将接收到的数据传给STC89C52芯片并通过数字显示屏LCD1602将接收的数据显示出来。然后再将数据上传给上位机,上位机可以实时显示温湿度,并且可以查看温度的历史数据。
简单的来说就是两块板子进行温湿度采集和显示,然后这两块板子将温湿度传给第三块板子,第三块板子可以进行显示两块板子的温湿度信息,然后第三块板子再把信息上传给上位机,在上位机可以实时查看温度和历史数据。
硬件电路设计
之前使用的单片机是集成好的开发板,并没有实际焊接过单片机系统,关于硬件电路这块我可能也只是知道电阻、电容、芯片等元器件焊接在什么位置。关于电路中的细节可能了解的不是那么的清楚。下图是最终焊接好的可以使用的成品。
STC89C52控制电路
单片机控制模块由单片机、晶振电路和复位电路构成。
- 晶振电路
晶振电路由两个30pF电容和一个12MHZ晶体振荡器构成,接入单片机的X1,X2引脚。 - 复位电路
单片复位端低电平有效。
温湿度传感器电路
DHT11是数字型温湿度传感器,可直接以数字方式传输所采集的当前环境温湿度,DHT11采用的是单总线通信,因此只需将单片机的一个I/O端口与DHT11的通信接口连接就可以实现数据的采集和传送,相对于其他电路来说比较简单。
LCD1602显示电路
对于显示电路网上的资料有很多,我们如果只是想使用它,那么我们只需要知道怎么接线就可以啦!知道对应的引脚的作用即可!
- 1脚: VSS为接地;
- 2脚: VDD接5V电源VCC;
- 3脚: VO为液晶显示器对比度调整;VO是液晶显示的偏压信号,可接10K 的3296精密电位器。或同样阻值的RM065/RM063蓝白可调电阻。
- 4脚: RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器;
- 5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS 和RW共同为低电平时可以写入指令或者显示地址,当RS为低电平RW为 高电平时可以读忙信号,当RS为高电平RW为低电平时可以写入数据;
- 6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令;
- 7~14脚:D0~D7为8位双向数据线;
- 15脚:背光电源正极;
- 16脚:背光电源负极。
NRF24L01无线传输电路
NRF24L01可工作于2.4GHz~2.5GHzISM频段,该收发器内置频率合成器、功率放大器、晶体振荡器、调制器等功能模块,是一款集成度较高的无线收发器。nRF24L01的外部电路比较简单,而且融合了增强型ShockBurst技术,其中输出功率和通信频道可通过程序进行配置。
该模块采用SPI同步串行通信接口,最大传输速率为10Mb/s,传输时先传送低位字节,再传送高位字节。但针对单个字节而言,要先送高位再送低位。与SPI相关的指令共有8个,使用时这些控制指令由NRF24L01的MOSI输入。相应的状态和数据信息是从MISO输出给MCU。
硬件电路测试
当我们焊接好板子以后一定不要直接上电测试,一定要先测试开发板的VCC和GND是否是断开的,如果两个短接很可能会导致开发板被烧掉。当测试VCC和GND没有短接后,我们需要进行测试芯片管脚是否焊接的有问题,是否出现虚焊、没有焊接、短路等问题,这里需要特别注意的是测试芯片管脚焊接是否有问题一定要将万用表的表笔轻轻的放在芯片管脚上面,如果用点力很可能将芯片管脚和底部的电路片给按压在一起导致测试出现问题。当测试芯片管脚没有问题以后我们可以给开发板上电测试串口电脑是否可以识别出端口号(一般没有出现问题插上电脑都会听到相应的提示音),如果出现无法识别的USB设备或者没有发现CH340串口号一般是串口芯片焊接有问题,或者电容焊接出现问题。当我们测试电脑可以识别串口后我们可以写点灯程序测试芯片是否可以控制IO口。如果测试发现对应的IO口灯不亮,很大可能是芯片管脚焊接出现问题!点灯测试程序如下:
#include "reg52.h"
sbit LED1=P1^0;
void main()
{
LED1=0;//点灯测试,如果测试P12可以改为LED1=P1^2
}
下位机软件程序设计
软件设计主要包括上位机的软件和下位机的软件程序设计,而下位机的软件程序又主要包括发送端和接送端的程序设计。我这里主要分享一下我对一些代码的理解和注意事项。
温湿度数据的读取
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。
位数据“0”的格式为: 50 微秒的低电平和 26-28 微秒的高电平,位数据“1”的格式为: 50 微秒的低电平加 70微秒的高电平。接收数据时可以先等待低电平过去,即等待数据线拉高,再延时60us,因为60us大于28us且小于70us,再检测此时数据线是否为高,如果为高,则数据判定为1,否则为0。
一次传送 40 位数据,高位先出 8bit 湿度整数数据 + 8bit 湿度小数数据+8bit 温度整数数据 + 8bit 温度小数数据+8bit 校验位
。
判断“8bit 湿度整数数据 + 8bit 湿度小数数据+8bit 温度整数数据 + 8bit 温度小数数据”的结果是否等于8bit 校验位。如果等于则数据接收正确,否则应该放弃这一次的数据,重新接收。
uchar data_byte;
uchar RH, RL, TH, TL;
void Delay_t(uint j)
{ uchar i;
for(;j>0;j--)
{
for(i=0;i<27;i++);
}
}
uchar receive_byte()//接收一个字节
{
uchar i,temp,count;
for(i=0;i<8;i++)
{
count=2;
while((!io)&&count++);//等待50us低电平结束
delay1();
delay1();
delay1();
temp=0;
if(io==1)
temp=1;
count=2;
while((io)&&count++);
if(count==1)
break;
data_byte<<=1;
data_byte|=temp;
}
return data_byte;
}
void receive()//接收数据
{
uchar T_H,T_L,R_H,R_L,check,num_check;
uchar count;
io=0;
Delay_t(180);
io=1;
delay1();
delay1();
delay1();
delay1();
io=1;
if(!io)//读取DHT11响应信号
{
count=2;
while((!io)&&count++);//DHT11高电平80us是否结束
count=2;
while((io)&&count++);
R_H=receive_byte();
R_L=receive_byte();
T_H=receive_byte();
T_L=receive_byte();
check=receive_byte();
io=1;
num_check=R_H+R_L+T_H+T_L;
if(num_check==check)
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
check=num_check;
}
}
}
从上面的驱动程序可以看出DHT11可以返回RH、RL、TH、TL分别对应的是湿度的高位、湿度的低位、温度的高位、温度的低位,在我们本次课程设计中并没有用到RL。知道返回数据的意义以后我们就可以进行计算温度和湿度了:hum=RH;tem=TH+TL*0.1;
。关于更多的DHT11的资料可以借鉴这个博客51单片机DHT11传感器
NRF24L01发送信息
void init_NRF24L01(void)//模块初始化
{
delay_us(100);
CE=0; //芯片使能
CSN=1; // spi失能(禁止24l01的spi功能)
SCK=0; // Spi时钟线初始化
IRQ = 1;
}
NRF24L01的初始化函数,主要完成四项工作:使能芯片、禁止使用SPI功能、以及时钟初始化、开启软件模拟中断!
void mcu_send()
{
delay(1500);
TxPacket(TxBuf);// 发送 TxBuf 中的数据
while(CheckACK());
LED=0;// 点亮LED以做提示
delay(100);
LED=1;// 熄灭LED
delay(100);
}
以上程序实现的是发送Txbuf中的数据并且等待应答,等待应答后进行闪灯提示。
void TxPacket(uchar * tx_buf)
{
CE=0;
SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH); //StandBy I模式
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // 装载接收端地址
SPI_Write_Buf(WR_TX_PLOAD, tx_buf, TX_PLOAD_WIDTH);
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // 频道0自动 ACK应答允许
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 允许接收地址只有频道0,如果需要多频道可以参考Page21
SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x0a);
SPI_RW_Reg(WRITE_REG + RF_CH, 0x36); // 设置信道工作为2.4GHZ,收发必须一致
//SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度,本次设置为4字节
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); // 装载数据
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e); // IRQ收发完成中断响应,16位CRC,主发送
CE=1; //置高CE,激发数据发送
delay_us(10);
}
以上代码主要是设置发送模式、设置发送数据长度、装载接收端的发送地址等功能,然后将tx_buf中的数据发送。
上图实现的就是点对点的无线通信!
NRF24L01接收数据
void RX_Mode(void)
{
CE=0;
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, RX_ADDRESS, RX_ADR_WIDTH); // 接收设备接收通道0使用和发送设备相同的发送地址
SPI_Write_Buf(WRITE_REG + RX_ADDR_P1, RX_ADDRESS1, RX_ADR_WIDTH); // 接收设备接收通道1使用和发送设备相同的发送地址
SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); // 接收通道0选择和发送通道相同有效数据宽度
SPI_RW_Reg(WRITE_REG + RX_PW_P1, RX_PLOAD_WIDTH); // 接收通道1选择和发送通道相同有效数据宽度
SPI_RW_Reg(WRITE_REG + EN_AA, 0x03); // 使能接收通道0,1自动应答
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x03); // 使能接收通道0,1
SPI_RW_Reg(WRITE_REG + RF_CH, 0x36); // 选择射频通道0x40
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); // 数据传输率1Mbps,发射功率0dBm,低噪声放大器增益*/
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0f); // CRC使能,16位CRC校验,上电,接收模式
CE = 1;
DelayMs(10);
}
以上代码主要是设置模式为接收模式,然后将接受设备和发送设备的地址写入通道,最后使能相关的通道设置频率和是否应答等。
uchar RxPacket(uchar * rx_buf)
{
uchar RevFlags = 5; //接收通道号
sta = SPI_Read(READ_REG + STATUS);//发送数据后读取状态寄存器
if(RX_DR) // 判断是否接收到数据
{
RevFlags = (sta & 0x0e); //读取数据完成标志0x00/0x02
CE = 0; //SPI使能
SPI_Read_Buf(RD_RX_PLOAD, rx_buf, RX_PLOAD_WIDTH);//break; // 从RXFIFO读取数据
CSN = 0;
SPI_RW(FLUSH_RX);//用于清空FIFO !!关键!!不然会出现意想不到的后果!!!大家记住!!
CSN = 1;
SPI_RW_Reg(WRITE_REG + STATUS, sta); //接收到数据后RX_DR,TX_DS,MAX_PT都置高为1,通过写1来清楚中断标
}
SPI_RW_Reg(WRITE_REG + STATUS, sta);
return(RevFlags);
}
这段代码主要执行的功能是从通道中读取数据,然后将数据写入到数组rx_buf数组中。注意当从通道中读取数据以后一定要清空FIFO,不然会出现难以预测的错误。
while (1)
{
RX_Mode();//两端的通信
if(RxPacket(mcu1_data)==0x00)
{
L1=0;
DelayMs(20);
L1=1;
DelayMs(20);
hum1=mcu1_data[0]*10+mcu1_data[1];
tem1=mcu1_data[2]*10+mcu1_data[3]+mcu1_data[4]*0.1;
}
DelayMs(10);
if(RxPacket(mcu2_data)==0x02)
{
L2=0;
DelayMs(20);
L2=1;
DelayMs(20);
hum2=mcu2_data[0]*10+mcu2_data[1];
tem2=mcu2_data[2]*10+mcu2_data[3]+mcu2_data[4]*0.1;
}
if(S4==0)
{
DelayMs(10); //消抖
if(S4==0)
{
while(!S4); //等待按键松开
LCD_Clear();//清除显示
num++;
if(num==3)
num=1;
}
}
if(num==1)
MCU1();
else if(num==2)
MCU2();
sprintf(uart_data,"%2.1f %2d %2.1f %2drn",tem1,hum1,tem2,hum2);
sendc(uart_data);
}
以上这段代码主要是关于主函数相关的代码,可以看出来是一个轮询系统!主要就是进行接收信息判断、按键扫描、串口发送数据等!
上位机软件设计
上位机我们使用的是Python的QT写的GUI界面,代码量较大我就不贴出来了吧,主要分享几个难点问题的解决思路吧!
实时从串口更新数据
当我们使用Pyqt进行GUI开发时会发现并不像我们单片机开发一样一个死循环的主程序,里面轮询执行各种程序,那么我们要实时动态更新串口数据就得用到多线程相关的知识。一个线程用于更新动态数据,另一个线程用于GUI界面的显示。关于Python多线程可以参考我的憨憨女友都能看懂学会的python多线程,里面有详细介绍多线程间通信的方式以及QThread。
# coding=utf-8
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.Qt import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import numpy as np
import matplotlib.pyplot as plt
import serial # 导入serial包
import sys
tem1=0.0
hum1=0
tem2=0.0
hum2=0
tim=0
class WorkThread(QThread):
# pyqtSignal是信号类
timeout = pyqtSignal() # 每隔一秒发送一个信号
speak = pyqtSignal() # 计数完成后发送一个信号
def run(self):
count = 0
playsound('answer.mp3')
while True:
# 休眠1秒
time.sleep(0.8)
count+=1
if count==5:
count=0
self.speak.emit()
self.timeout.emit() # 发送timeout信号
class MyClass(QWidget):
def __init__(self):
self.workThread = WorkThread()
self.workThread.timeout.connect(self.temperature)
self.workThread.speak.connect(self.speak_data)
self.workThread.start()
def speak_data(self):
global tem1,tem2
global tim
tim+=1
f = open('E:/大学课程/无线通信原理课程设计/data/MCU1_DATA.txt','a')
f.writelines([str(tim),' ',tem1,' ',tem2,'n'])
#print(1)
def temperature(self):
global tem1
global hum1
global tem2
global hum2
self.serial.flushInput()
n = self.serial.readline() # serial包的用法 读取串口的一行数据
self.data_pre = str(n)
print(n)
hum1 = self.data_pre[15:17] # 取数据的一部分放进data变量
tem1 = self.data_pre[9:14]
hum2 = self.data_pre[7:9]
tem2 = self.data_pre[2:6]
self.lcdNumber.display(tem1)
self.humidity.display(hum1)
self.lcdNumber1.display(tem2)
self.humidity1.display(hum2)
以上是部分代码,主要是关于动态更新数据以及多线程间通信相关。WorkThread每0.8秒更新一次串口数据,每3.2秒将串口的数据写入到指定位置的文件中!
从串口读取数据前一定要执行打开串口的操作!并且设置串口的波特率以及串口读取数据到缓冲区的频率具体代码如下:
self.serial = serial.Serial('COM17', 9600, timeout=0.8) # 打开串口,后面是串口配置 #连接COM5,波特率位9600
if self.serial.isOpen():
print('串口已打开')
else:
print('串口未打开')
注意:这里建议timeout最好和多线程中设置的读取串口数据时间相同,如果过快会导致很多数据来不及读取,读取过慢会导致大量数据滞留于缓冲区,更新数据不及时。
绘制历史数据
一开始本来想着动态绘制历史数据的,但是网上关于动态绘图的例程大多数都是在一个循环中,最后用了一个plt.pause(1)
来实现延时的作用。但是使用plt.pause(1)
的作用不仅仅是延时的作用,它还有关闭原来界面然后在原来界面的基础上重新绘制新的数据,从而达到动态绘图的目的!
显然通过这种方式来绘制波形就得再开启一个线程,一个程序有多个线程会大大增加程序的不稳定性,这样定是不划算的!显然将数据保存在文件中,当按钮点击以后按照指定格式读取文件中的内容这种方式更加安全和方便!
历史数据格式如下:
def historical_data(self):
plt.xlabel('time')
plt.ylabel('temperature')
plt.title('history data')
data = np.loadtxt('E:/大学课程/无线通信原理课程设计/data/MCU1_DATA.txt')
plt.plot(data[:, 0], data[:, 1], label='Frist line', linewidth=3, color='r', marker='o', markerfacecolor='blue',
markersize=12)
plt.plot(data[:, 0], data[:, 2], label='second line')
plt.show()
以上就是最终的上位机设计界面,可以看出可以实时的显示温湿度以及历史数据!
总结
历时两周的课程设计的基础功能算是已经完成了,达到了自己的预期!但是中途遇见了很多问题,关于开发板的焊接、调试、方案选择等问题上挺让人的崩溃的。但是通过本次课程设计让我做事情更加的拥有耐心、更加深入的理解了单片机和多线程相关知识。知道如何对硬件进行测试,不仅仅局限于软件层面!文章最后感谢我的两个队友以及对我有帮助的老师和同学们!
如果有想要程序的或者有技术问题的可以在我的博客私信咨询我!!!
如果有想要程序的或者有技术问题的可以在我的博客私信咨询我!!!
如果有想要程序的或者有技术问题的可以在我的博客私信咨询我!!!
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
最后
以上就是内向老虎为你收集整理的基于无线通信的多点温湿度采集系统设计的全部内容,希望文章能够帮你解决基于无线通信的多点温湿度采集系统设计所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复