串行接口可靠通信系统设计

重庆大学-微电子与通信工程学院-通信工程专业-大二夏季小学期课程设计-通信接口技术实践


前言

软件

  1. Proteus(尽量下载最新版本+汉化,降低上手门槛);
  2. Arduino IDE (可以设置成中文);
  3. Commix(串口调试助手);
  4. 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七层模型中的第二层)中传输的数据单元。一个帧包含了用于通信的控制信息和实际要传输的数据。帧是网络通信中数据传输的基本单位,它确保了数据在物理媒介上的有序和可靠传输。

  • 主要结构
    1. 帧头(Header):帧头包含了用于帧识别和处理的控制信息,如源地址、目的地址、类型标识、帧长度或计数等。
    2. 数据字段(Payload or Data Field):这是帧的主要部分,包含了上一层(通常是网络层)的数据包或分组,也就是实际要传输的应用层数据。
    3. 帧尾(Footer)或帧检验序列(Frame Check Sequence, FCS):帧尾通常包含一个用于错误检测的字段,如CRC(循环冗余校验),它可以帮助接收端判断数据是否在传输过程中发生错误。
  • 作用
    1. 封装:将高层的数据封装成适合在物理层传输的格式。
    2. 同步:提供开始和结束标志,使接收端能够识别帧的边界。
    3. 差错检测:通过校验和或CRC等机制,检测数据传输过程中可能产生的错误。
    4. 寻址:帧头中的地址信息确保数据被正确地发送到目的地。
    5. 流量控制:在某些情况下,帧可以包含流量控制信息,以避免接收端过载。

ACK确认帧Acknowledgment Frame):是在数据通信和网络传输中用来确认数据包已成功接收的信号。在通信协议中,当一方(接收端)成功接收到另一方(发送端)发送的数据包后,接收端会回传一个ACK确认帧给发送端,以此来告知发送端数据已被正确接收,无需重新发送。

1.2 提高要求

1.参考Modbus协议中的CRC算法,实现帧的无差错接收

CRC,即循环冗余校验Cyclic Redundancy Check),是一种广泛应用于数字网络通信和存储系统的错误检测算法。其主要目的是检测数据传输过程中的错误,而不需要大量的额外信息(冗余)。它通过在数据后面附加一个校验码,接收方可以使用相同的算法来验证数据的完整性。
CRC校验在Modbus协议中通常使用的是CRC-16CRC-CCITT(CRC-16-Modbus) 算法,这是一种16位的校验方式。

样例演示
在这里插入图片描述

  • 工作原理
    1. 确定生成多项式:Modbus协议通常使用CRC-16多项式,其表达式为 x 16 + x 15 + x 2 + 1 x^{16}+x^{15}+x^{2}+1 x16+x15+x2+1或十六进制表示为0x8005。
    2. 数据编码:发送方将要传输的数据看作一个大的二进制数,并通过模2除法(类似于普通除法,但使用异或操作代替减法,没有进位)与生成多项式进行运算,得到一个余数。
    3. 附加校验码:发送方将得到的余数(CRC校验码)附加到原始数据的末尾,形成一个完整的发送数据包。
    4. 接收方验证:接收方收到数据包后,使用同样的生成多项式对数据进行模2除法。如果数据在传输过程中没有发生改变,最终的余数应该是零(或与发送方的CRC校验码相匹配),这意味着数据被无误地接收。
    5. 错误检测:如果余数不是零,这表明数据在传输过程中发生了错误,接收方可以要求发送方重传数据。

2.实物板利用报文(帧),实现虚拟板不少于3个不同颜色LED灯的时间、颜色以及个数的灵活控制。
3.利用人机界面(菜单)实现要求2中的参数设置

二、模块设计与验证

2.1 发送端(传感器)

图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);
}

三、联合调试与总体测试

  • 发送端
图1 发送端的RXD

图1 发送端的RXD

图2 发送端的TXD

请添加图片描述
6个字节为一帧,以帧的形式传输数据,向接收端发送数据帧后会返回ACK帧,ACK帧含0x4F,0x4B,分别为字母“O”和“K”的ASCII码值。

图3 发送数据失败

请添加图片描述
数据帧发送失败,没有接收到ACK帧,会返回“Warning: Max retries reached without receiving ACK.”

  • 接收端
图4 LCD显示屏(10cm)

请添加图片描述

图5 人体距传感器10cm

请添加图片描述
人体距传感器10cm时,传感器输出数值超出所设阈值(threshold):400,LCD显示有物体接近。

图6 人体距传感器13cm

请添加图片描述

图7 LCD显示屏(13cm)

请添加图片描述
人体距传感器13cm,传感器输出数值未超过阈值,LCD显示没有检测到物体。

不足:该TFT显示元件是会一直显示,在相同的位置再次输出显示,之前的内容不会消失,而是会和新内容重合显示,带来麻烦。该问题解决后,出现屏幕刷新率慢,内容更新不及时的问题,且要更新的内容越多,刷新率越慢。

改进:使用millis()函数代替delay()函数,避免阻塞程序运行。


总结

  • 时间安排
    有两周的时间,每天都要做一些,否则拖到后面时间很紧张。
  • 合作交流
    自己一个人做的效率没有和别人交流来的效率高,可以的话尽量大家线下一起做,相互沟通交流。
  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qiming_Peng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值