重庆大学-微电子与通信工程学院-通信工程专业-大二夏季小学期课程设计-通信接口技术实践
前言
软件
- Proteus(尽量下载最新版本+汉化,降低上手门槛);
- Arduino IDE (可以设置成中文);
- Commix(串口调试助手);
- Configure Virtual Serial Port Driver(创建虚拟串口)。
因能力有限,虚拟板和实物板的通信 与 提高要求未实现。
一、需求分析
基于Arduino平台设计一个异步全双工串行接口下的可靠通信系统,该系统依靠可靠通信方式将数据和命令进行传输,并利用传输的数据或命令完成控制功能,同时设计友好的人机交互界面。
• 串行通信 :是一种数据传输方式,其中数据位被逐个顺序发送,而不是像并行通信那样同时发送多个位。
• 全双工 :则表示数据可以同时在两个方向上传输,即发送和接收可以同时进行。
• 异步 :意味着没有共享的时钟信号来同步数据的发送和接收。
1.1 基本要求
1.掌握Arduino与Proteus的基本操作,实现Arduino板的系统仿真
Arduino板的系统仿真是一种在没有实际硬件的情况下测试和验证Arduino程序的方法。
2.Arduino I/O口控制,实现2种传感器数据的采集
此次采用DHT11温湿度传感器和红外线传感器来实现。
3.利用系统时间函数实现定时功能
millis()
是Arduino环境中一个非常重要的函数,它用于获取自Arduino开发板上电或复位后所经历的时间,单位为毫秒。这个函数返回一个无符号长整型(unsigned long)值,表示从开发板启动到现在的时间长度。
在Arduino的程序中,millis()
函数经常用来实现定时功能,尤其是在需要避免使用delay()
函数的情况下,后者会阻塞整个程序的执行。millis()
配合条件语句可以实现非阻塞性的延时,允许程序在等待特定时间的同时继续执行其他任务,这对于需要响应实时事件(如传感器读取、串行通信等)的应用特别有用。
4.在Proteus仿真端实现LCD显示
采用TFT实现LCD显示。
5.多字节封装为帧,通过串口(UART)实现两块虚拟板的通信
帧(frame): 在数据通信和网络技术中,“帧”是指在数据链路层(OSI七层模型中的第二层)中传输的数据单元。一个帧包含了用于通信的控制信息和实际要传输的数据。帧是网络通信中数据传输的基本单位,它确保了数据在物理媒介上的有序和可靠传输。
- 主要结构
- 帧头(Header):帧头包含了用于帧识别和处理的控制信息,如源地址、目的地址、类型标识、帧长度或计数等。
- 数据字段(Payload or Data Field):这是帧的主要部分,包含了上一层(通常是网络层)的数据包或分组,也就是实际要传输的应用层数据。
- 帧尾(Footer)或帧检验序列(Frame Check Sequence, FCS):帧尾通常包含一个用于错误检测的字段,如CRC(循环冗余校验),它可以帮助接收端判断数据是否在传输过程中发生错误。
- 作用
- 封装:将高层的数据封装成适合在物理层传输的格式。
- 同步:提供开始和结束标志,使接收端能够识别帧的边界。
- 差错检测:通过校验和或CRC等机制,检测数据传输过程中可能产生的错误。
- 寻址:帧头中的地址信息确保数据被正确地发送到目的地。
- 流量控制:在某些情况下,帧可以包含流量控制信息,以避免接收端过载。
ACK确认帧(Acknowledgment Frame):是在数据通信和网络传输中用来确认数据包已成功接收的信号。在通信协议中,当一方(接收端)成功接收到另一方(发送端)发送的数据包后,接收端会回传一个ACK确认帧给发送端,以此来告知发送端数据已被正确接收,无需重新发送。
1.2 提高要求
1.参考Modbus协议中的CRC算法,实现帧的无差错接收
CRC,即循环冗余校验(Cyclic Redundancy Check),是一种广泛应用于数字网络通信和存储系统的错误检测算法。其主要目的是检测数据传输过程中的错误,而不需要大量的额外信息(冗余)。它通过在数据后面附加一个校验码,接收方可以使用相同的算法来验证数据的完整性。
CRC校验在Modbus协议中通常使用的是CRC-16或CRC-CCITT(CRC-16-Modbus) 算法,这是一种16位的校验方式。
样例演示
- 工作原理
- 确定生成多项式:Modbus协议通常使用CRC-16多项式,其表达式为 x 16 + x 15 + x 2 + 1 x^{16}+x^{15}+x^{2}+1 x16+x15+x2+1或十六进制表示为0x8005。
- 数据编码:发送方将要传输的数据看作一个大的二进制数,并通过模2除法(类似于普通除法,但使用异或操作代替减法,没有进位)与生成多项式进行运算,得到一个余数。
- 附加校验码:发送方将得到的余数(CRC校验码)附加到原始数据的末尾,形成一个完整的发送数据包。
- 接收方验证:接收方收到数据包后,使用同样的生成多项式对数据进行模2除法。如果数据在传输过程中没有发生改变,最终的余数应该是零(或与发送方的CRC校验码相匹配),这意味着数据被无误地接收。
- 错误检测:如果余数不是零,这表明数据在传输过程中发生了错误,接收方可以要求发送方重传数据。
2.实物板利用报文(帧),实现虚拟板不少于3个不同颜色LED灯的时间、颜色以及个数的灵活控制。
3.利用人机界面(菜单)实现要求2中的参数设置
二、模块设计与验证
2.1 发送端(传感器)
采集传感器的数据,发送到接收端处理并且显示来实现交互。
元件: DHT11温湿度传感器;虚拟终端(Virtual Terminal);红外线传感器(Grove Infrared Proximity Sensor);COMPIM。
红外线传感器(GP2Y0A21YK0F): 能检测10cm~80cm的距离,距离不同输出电压不同。通过设定阈值(threshold)来判断是否有人接近。
代码: DHT11与红外线传感器的直接调用Arduino IDE的示例工程代码。(优先在Proteus里面找,如果没有代码只有FlowChart的话再去Arduino IDE查找)
完整代码
// 发送端
#include <HardwareSerial.h>
#include <DHT11.h>
DHT11 dht11(8);
const int sensorPin = A0;
const int threshold = 400;
#define START_FLAG 0x7E
#define END_FLAG 0xEF
#define TRANSMIT 0x01
#define RECEIVE 0x02
#define Temperature 0x01
#define Humidity 0x02
#define Infrared 0x03
#define DATA_FRAME_TYPE 0x01
#define ACK_FRAME_TYPE 0x02
#define RETRY_DELAY 250
#define MAX_RETRIES 3
void setup() {
Serial.begin(9600);
pinMode(sensorPin,INPUT);
delay(2000); // 给接收端准备好
}
void loop() {
// 帧定义
byte dataType = 0x22;
byte data= 0x33;
byte frame[6] = {START_FLAG, TRANSMIT, DATA_FRAME_TYPE, dataType, data, END_FLAG};
//检测人接近
infraredSensor(frame);
// 检测温度
temperatureSensor(frame);
// 检测湿度
humiditySensor(frame);
}
// 函数:发送帧
void sendFrame(byte *frame, int size) {
//Serial.write(frame,size);
int retries = 0;
bool ackReceived = false;
while(!ackReceived && retries < MAX_RETRIES){
Serial.write(frame,size);
delay(RETRY_DELAY);
ackReceived = checkForAck();
retries++;
//ackReceived = true;
}
if(!ackReceived){
Serial.println("Warning: Max retries reached without receiving ACK.");
}
}
// 函数:Ack检查
bool checkForAck(){
byte buffer[6];
while(Serial.available()>0){
Serial.readBytes(buffer,6);
if(buffer[0]==START_FLAG){
if(buffer[3]==0x4F&&buffer[4]==0x4B){ //对应OK
return true;
break;
}
}
}
return false;
}
// 函数:检测人接近
void infraredSensor(byte *frame){
int sensorValue = analogRead(sensorPin);
frame[3] = Infrared;
// 检测到接近
if(sensorValue > threshold){
/*
Serial.println("Object is close.");
Serial.print("sensorValue:");
Serial.println(sensorValue);
*/
frame[4] = 0x01;
sendFrame(frame,6);
}
else{
// 没有物体接近
/*
Serial.println("No Object detected.");
Serial.print("sensorValue:");
Serial.println(sensorValue);
*/
frame[4] = 0x00;
sendFrame(frame,6);
}
}
// 函数:检测温度
void temperatureSensor(byte *frame){
int temperature = 0;
int humidity = 0;
int result = dht11.readTemperatureHumidity(temperature, humidity);
// 检测成功
if(result == 0){
/*
Serial.print("Temperature: ");
Serial.print(temperature);
*/
frame[3] = Temperature;
frame[4] = temperature;
sendFrame(frame,6);
}
// 检测失败
else{
Serial.println(DHT11::getErrorString(result));
}
}
// 函数:检测湿度
void humiditySensor(byte *frame){
int temperature = 0;
int humidity = 0;
int result = dht11.readTemperatureHumidity(temperature, humidity);
// 检测成功
if(result == 0){
/*
Serial.print(" °C\tHumidity: ");
Serial.print(humidity);
Serial.println(" %");
*/
frame[3] = Humidity;
frame[4] = humidity;
sendFrame(frame,6);
}
// 检测失败
else{
Serial.println(DHT11::getErrorString(result));
}
}
2.2 接收端(显示)
接收处理传感器的数据,显示信息在LCD上。
元件: ST7735R的TFT LCD显示屏;虚拟终端(Virtual Terminal);COMPIM。
完整代码
// 接收端
#include <HardwareSerial.h>
#include <TFT.h> // Arduino LCD library
#include <SPI.h>
#define cs 10
#define dc 9
#define rst 8
#define START_FLAG 0x7E
#define END_FLAG 0xEF
#define TRANSMIT 0x01
#define RECEIVE 0x02
#define Temperature 0x01
#define Humidity 0x02
#define Infrared 0x03
#define DATA_FRAME_TYPE 0x01
#define ACK_FRAME_TYPE 0x02
#define RETRY_DELAY 300
#define MAX_RETRIES 3
// create an instance of the library
TFT TFTscreen = TFT(cs, dc, rst);
void setup() {
Serial.begin(9600);
//TFT 显示
// Put this line at the beginning of every sketch that uses the GLCD:
TFTscreen.begin();
// 背景
TFTscreen.background(255, 255, 255);
// 笔刷
TFTscreen.stroke(105, 105, 105);
// set the font size
TFTscreen.setTextSize(1);
// write the text to the top left corner of the screen
TFTscreen.text("Start a focused day", 0, 0);
TFTscreen.text("Humidity:", 0, 10);
TFTscreen.text("Temperature:", 0, 20);
TFTscreen.text("Infrared:", 0, 30);
}
void loop() {
// 获取帧
int flag = 0;
byte frame[6];
if(Serial.available()){
Serial.readBytes(frame,6);
if(frame[0]==START_FLAG&&frame[5]==END_FLAG){
sendAck();
flag=1;
}
}
// 理解帧
if(flag==1){
// 红外线
if(frame[3]==Infrared){
infraredShow(frame);
}
// 温度
if(frame[3]==Temperature){
temperatureShow(frame);
}
//湿度
if(frame[3]==Humidity){
humidityShow(frame);
}
}
}
void sendAck(){
byte ackFrame[6] = {START_FLAG, RECEIVE, ACK_FRAME_TYPE, 0x4F, 0x4B, END_FLAG};
Serial.write(ackFrame,6);
}
// 函数:红外线人体检测
void infraredShow(byte *frame){
String str = "";
if(frame[4]==0x01){
str = "Object is close.";
}
if(frame[4]==0x00){
str = "No Object detected.";
}
// 显示到屏幕
char strChars[20];
str.toCharArray(strChars,20); // 转成数组才能显示
TFTscreen.stroke(0, 0, 0);
TFTscreen.text(strChars, 40, 40);
delay(250);
// erase the text you just wrote(否则不会更新)
TFTscreen.stroke(255, 255, 255);
TFTscreen.text(strChars, 40, 40);
}
// 函数:温度检测
void temperatureShow(byte *frame){
String str = String(int(frame[4]));
// 显示到屏幕
char strChars[5];
str.toCharArray(strChars,5); // 转成数组才能显示
TFTscreen.stroke(0, 0, 0);
TFTscreen.text(strChars, 90, 20);
// 显示单位
str = "Cel";
str.toCharArray(strChars,5);
TFTscreen.text(strChars, 105, 20);
}
// 函数:湿度检测
void humidityShow(byte *frame){
String str = String(int(frame[4]));
// 显示数值到屏幕
char strChars[5];
str.toCharArray(strChars,5); // 转成数组才能显示
TFTscreen.stroke(0, 0, 0);
TFTscreen.text(strChars, 90, 10);
// 显示单位
str = "%";
str.toCharArray(strChars,5);
TFTscreen.text(strChars, 105, 10);
}
三、联合调试与总体测试
- 发送端
6个字节为一帧,以帧的形式传输数据,向接收端发送数据帧后会返回ACK帧,ACK帧含0x4F,0x4B,分别为字母“O”和“K”的ASCII码值。
数据帧发送失败,没有接收到ACK帧,会返回“Warning: Max retries reached without receiving ACK.”
- 接收端
人体距传感器10cm时,传感器输出数值超出所设阈值(threshold):400,LCD显示有物体接近。
人体距传感器13cm,传感器输出数值未超过阈值,LCD显示没有检测到物体。
不足:该TFT显示元件是会一直显示,在相同的位置再次输出显示,之前的内容不会消失,而是会和新内容重合显示,带来麻烦。该问题解决后,出现屏幕刷新率慢,内容更新不及时的问题,且要更新的内容越多,刷新率越慢。
改进:使用millis()
函数代替delay()
函数,避免阻塞程序运行。
总结
- 时间安排
有两周的时间,每天都要做一些,否则拖到后面时间很紧张。 - 合作交流
自己一个人做的效率没有和别人交流来的效率高,可以的话尽量大家线下一起做,相互沟通交流。