STM32复习(四)--- RS485串口通讯

一、RS485通讯协议简介

        RS485(也称为 EIA-485TIA-485)是一种常用于工业和通信领域的串行通信标准。它定义了一个电气特性规范,用于在多点网络中进行数据传输,特别适用于需要长距离、高速传输和抗干扰的应用场景。RS485标准常用于工业自动化、传感器、智能仪表等系统中。

特点:

        1.差分信号传输

        RS485使用差分信号进行数据传输。它由两根信号线(A和B)组成:

  • A线(+)B线(-) 之间的电压差传输数据。
  • 当电流在这两根线之间的方向发生变化时,接收端会读取这种变化并解码为数据位。

        相比单端信号,差分信号对噪声的抗干扰能力更强,因为噪声通常影响两条线的相同程度,因此它们之间的电压差不会受到噪声的显著影响。

        2.多点通信
        RS485支持多达32个设备(发送器和接收器总和)连接在同一总线上。这使得它非常适合于多节点的网络应用。

        3.长距离传输
        RS485的通信距离可以达到1200米(约4000英尺),这取决于传输速率和电缆质量。在低速率下(如9600bps),可以实现更长的传输距离。

        4.高传输速率
        RS485支持较高的传输速率(如10 Mbps),尽管速度和距离通常是相互制约的。

         5.抗干扰能力强
        由于使用差分信号,RS485能在电磁干扰(EMI)较强的环境下稳定工作,因此常用于工厂、自动化设备和恶劣环境下的通信。

RS-485与RS-232通讯协议的特性对比:  

通讯标准信号线通讯方向电平标准通讯距离通讯节点数
RS232TXD,RXD,GND全双工

逻辑1:+3V ~ +15V

逻辑0:-15V ~ -3V

100米以内只有两个节点
RS485差分线AB半双工

逻辑1:+2V ~ +6V

逻辑0:-6V ~ -2V

1200米支持多个节点,支持多个主设备,任意节点间可以相互通讯

差分信号线具有很强的干扰能力,特别适合应用于电磁环境复杂的工业控制环境中,RS-485协议主要是把RS-232的信号改进成差分信号,从而大大提高了抗干扰特性

二、 RS485通讯实验

 本实验我们采用土壤传感器,RS485转TTL模块以及STM32F103C8T6最小系统板来实现。

1.土壤传感器

以下是土壤传感器的部分参数:

这个协议内容用到了ModBus-Rtu,感兴趣的小伙伴可以看一下这个https://blog.csdn.net/as480133937/article/details/123197782

2.RS485转TTL模块

实验原理:
485通讯实质上就是软件层的串口通讯,再加上物理层上的485芯片,将TTL信号转化为差分信号进行传输,对待上就按照串口通讯就行。
编程思想:
DE: 1 发送使能;0发送禁止
RE: 0 接收使能;1接收禁止

一般将DE和RE连在一起,这样就是高电平发送,低电平接收

通过单片机串口2的TXD(PA2)将数据发送出去,RXD(PA3)进行接收数据,然后通过串口1打印接收到的数据 。

由于是通过485协议发送数据,每次发送前要对485传输方式设置为发送模式,完成后要改为接收模式,由于是半双工,发送完要有一定的延时,确保数据不会丢失;

 3.接线图

4、程序代码

Usart.h

#ifndef __USART_H__
#define __USART_H__

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

void my_Usart_Init2(void);
void my_Usart_Init(void);
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data);  //发送一个字节
void My_Usart_SendArray(uint8_t *Array,uint16_t Length);  //发送一个数组(用串口2)
void My_Usart_SendArray2(uint8_t *Array,uint16_t Length); //发送一个数组(用串口1)
void My_Usart_Send_String(USART_TypeDef* USARTx, char * str); //发送字符串
uint8_t Serial_GetRxFlag(void);  //实现读后自动清除的功能

#endif

Usart.c

#include "stm32f10x.h"
#include "Usart.h"
#include "stdio.h"
#include "RS485.h"

uint8_t Serial_TxPacket[8];
uint8_t Serial_RxPacket[12];
uint8_t Serial_RxFlag;

void my_Usart_Init2(void)   //初始化串口2
{
  GPIO_InitTypeDef gpio_initstruct;
	USART_InitTypeDef usart_initstruct;
	NVIC_InitTypeDef nvic_initstruct;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	
//Tx  PA2
	gpio_initstruct.GPIO_Mode  = GPIO_Mode_AF_PP;//发送数据固定为复用推挽输出
	gpio_initstruct.GPIO_Pin   = GPIO_Pin_2;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_initstruct);
//Rx  PA3	
	gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;//接收数据为浮空输入或上拉输入
	gpio_initstruct.GPIO_Pin  = GPIO_Pin_3;
	GPIO_Init(GPIOA,&gpio_initstruct);
	
	usart_initstruct.USART_BaudRate = 9600;
	usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用流控
	usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	usart_initstruct.USART_Parity = USART_Parity_No;//不使用校验位
	usart_initstruct.USART_StopBits = USART_StopBits_1;//一位停止位
	usart_initstruct.USART_WordLength = USART_WordLength_8b;//因为不使用校验位所以字节长度为八个
	USART_Init(USART2,&usart_initstruct);
	
	
	USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
	
	nvic_initstruct.NVIC_IRQChannel = USART2_IRQn;
	nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 2;
	nvic_initstruct.NVIC_IRQChannelSubPriority = 2;
	nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvic_initstruct);
	
	USART_Cmd(USART2,ENABLE);//一定记得配置完后使能USART2外设
}


void my_Usart_Init(void)    //初始化串口1
{
	GPIO_InitTypeDef gpio_initstruct;
	USART_InitTypeDef usart_initstruct;
	NVIC_InitTypeDef nvic_initstruct;
		
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE);
	
	
//Tx  PA9
	gpio_initstruct.GPIO_Mode  = GPIO_Mode_AF_PP;//发送数据固定为复用推挽输出
	gpio_initstruct.GPIO_Pin   = GPIO_Pin_9;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_initstruct);
//Rx  PA10	
	gpio_initstruct.GPIO_Mode = GPIO_Mode_IPU;//接收数据为浮空输入或上拉输入
	gpio_initstruct.GPIO_Pin  = GPIO_Pin_10;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&gpio_initstruct);
	
	usart_initstruct.USART_BaudRate = 115200;
	usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用流控
	usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	usart_initstruct.USART_Parity = USART_Parity_No;//不使用校验位
	usart_initstruct.USART_StopBits = USART_StopBits_1;//一位停止位
	usart_initstruct.USART_WordLength = USART_WordLength_8b;//因为不使用校验位所以字节长度为八个
	USART_Init(USART1,&usart_initstruct);
	
	USART_Cmd(USART1,ENABLE);//一定记得配置完后使能USART1外设
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	nvic_initstruct.NVIC_IRQChannel = USART1_IRQn;
	nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 1;
	nvic_initstruct.NVIC_IRQChannelSubPriority = 1;
	nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvic_initstruct);
	
}

//发送一个字节
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data)
{
  USART_SendData(USARTx, Data);
	while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);//‘发送数据寄存器空’等于0,即说明寄存器中还有数据,因此需要留在while循环中

}
//发送一个数组
void My_Usart_SendArray(uint8_t *Array,uint16_t Length)
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		My_Usart_Send_Byte(USART2,Array[i]);
	}
}
//发送一个数组
void My_Usart_SendArray2(uint8_t *Array,uint16_t Length)
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		My_Usart_Send_Byte(USART1,Array[i]);
	}
}
//发送一个字符串
void My_Usart_Send_String(USART_TypeDef* USARTx, char * str)
{
	uint16_t i=0;
	do
	{
		
		My_Usart_Send_Byte(USARTx,*(str+i));//逐个字符发送
		i++;
	
	}while(*(str+i) != '\0');// 直到遇到字符串结束符
	while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);

}

uint8_t Serial_GetRxFlag(void)//实现读后自动清除的功能
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag=0;
		return 1;
	}
	return 0;
}

int fputc(int ch,FILE*f)//对printf函数重定向
{
	My_Usart_Send_Byte(USART1,ch);
	return ch;
}

void USART1_IRQHandler(void)//中断函数名称看开始文件
{
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
	{

		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除标志位,如果读取DR是可以自动清除的
	}
}

void USART2_IRQHandler()
{
	if(USART_GetITStatus(USART2,USART_IT_RXNE) == SET)
	{
		static uint8_t RxState = 0;
		static uint8_t pRxState = 0;
		
		uint8_t RxData = USART_ReceiveData(USART2);
		
		if(RxState == 0)
		{
			if(RxData==0x01) //如果地址是0x01再接收其他返回的数据并存在Serial_RxPacket数组里
			{
				RxState=1;
				pRxState=0;
			}
		}
		else if(RxState == 1)
		{
			Serial_RxPacket[pRxState]=RxData;
			
			pRxState ++;
			
			if(pRxState>=12)
			{
					RxState=0;
					Serial_RxFlag=1;
			}
		}

		USART_ClearITPendingBit(USART2,USART_IT_RXNE);//清除标志位,如果读取DR是可以自动清除的
	}
}

这样我们就把发送接收数据的串口2和打印数据的串口1配置好了,接下来配置控制RS485发送还是接收的PA7

RS485.h

#ifndef __RS485_H__
#define __RS485_H__

void RS485_Init(void);

#endif

RS485.c

#include "stm32f10x.h"
#include "RS485.h"

void RS485_Init(void)
{
	GPIO_InitTypeDef gpio_initstruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	gpio_initstruct.GPIO_Mode  = GPIO_Mode_Out_PP;
	gpio_initstruct.GPIO_Pin   = GPIO_Pin_7;       
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA,&gpio_initstruct);

}

main.c

#include "stm32f10x.h" // Device header
#include "stdio.h"
#include "dht11.h"
#include "delay.h"
#include "Usart.h"
#include "RS485.h"
#include "relay.h"
#include "OLED.h"

int main(void)
{
	
	Relay_Init();
	my_Usart_Init();
	my_Usart_Init2();
	RS485_Init();
	
	Serial_TxPacket[0]=0x01;
	Serial_TxPacket[1]=0x03;
	Serial_TxPacket[2]=0x00;
	Serial_TxPacket[3]=0x00;
	Serial_TxPacket[4]=0x00;
	Serial_TxPacket[5]=0x04;
	Serial_TxPacket[6]=0x44;
	Serial_TxPacket[7]=0x09;
	
	
	u8 tem1=0;
	u8 hum1=0;
	
	float temp=0;
	float humi=0;
	 u16  EC=0;
	float PH=0;
	
	
	while(1)
	{
		delay_s(5); //5s采集一次数据
		GPIO_SetBits(GPIOA,GPIO_Pin_7); //给高电平,设置为发送模式
		My_Usart_SendArray(Serial_TxPacket,8); //将询问帧发给土壤传感器
		delay_ms(1); //发送完延时1ms保证数据不会丢失
		GPIO_ResetBits(GPIOA,GPIO_Pin_7); //给低电平,设置为接收模式,接收土壤传感器返回的数据
		if(Serial_GetRxFlag() == 1) //如果返回的数据满足中断函数的要求将会返回1
		{
			temp  = ((float)(Serial_RxPacket[4]*256 + Serial_RxPacket[5]))/10.0;
			humi  = ((float)(Serial_RxPacket[2]*256 + Serial_RxPacket[3]))/10.0;
			 EC   = Serial_RxPacket[6]*256 + Serial_RxPacket[7];
			 PH   = ((float)(Serial_RxPacket[8]*256 + Serial_RxPacket[9]))/10.0;
			printf("\r\ntemp: %.1f \r\nhumi: %.1f \r\nEC: %d \r\nPH: %.1f \r\n",temp,humi,EC,PH);
			
		} //打印收到的各个数据
		
            
            if(humi <= 30)
			{
				GPIO_ResetBits(GPIOA,GPIO_Pin_0);
			}
			else
			{
				GPIO_SetBits(GPIOA,GPIO_Pin_0);
			}
		
		//这个后续可以加一个继电器来控制水泵的开关,如果湿度过低就打开水泵,这里就先不加了
		
		
		
	}
}

三、串口助手显示 

这个Airtemp和Airhumi是我测试的空气温度和湿度,可以不看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值