STM32F103(二十四)一篇博客精通《485通信》

学习板:STM32F103ZET6

参考:

STM32F103(二十三)通用同步异步收发器(USART)

485通信

  • 一、485简介
    • 1、简述
    • 2、芯片简述
    • 3、芯片测试
    • 4、面向原理图
  • 二、手把手写信号传递函数
    • 1、写一个发送一次u8数据的函数
    • 2、写一个发送多个u8数据的函数
    • 3、写一个接收多个数据函数的函数
  • 三、例题(F1板子发送——>F4板子接收)
    • 1、题
    • 2、分析
    • 3、F1板子代码(发送数据)
    • 4、F4板子代码(接收数据并串口打印)
    • 5、引脚连接
    • 6、测试
  • 四、总结

一、485简介

1、简述

485是一种2线、半双工的通信,传输距离可达到1Km以上,通过A线、B线两线的电压差来传递信号。

485芯片接口电平很低,可以兼容TTL电路,所以可以直接接入到TTL电路。发送信号“1”时两线电压差为+(2~6)V,发送信号“0”时两线电压差为 -(2 ~6)V。

因为需要根据A线、B线两端电压差来确定逻辑“1”和“0”,所以收发双方都需要接一个匹配电阻,一般为120Ω。

2、芯片简述

485芯片一般为以下框图:( SP3485为例)
STM32F103(二十四)一篇博客精通《485通信》_第1张图片

包括我近期项目选用的一款485芯片:

STM32F103(二十四)一篇博客精通《485通信》_第2张图片

其中:
RO:接收数据输出端,485通信过程中,终端信号接收引脚。

RE:接收数据使能,低电平有效,终端的485芯片RE引脚置0,进入接收模式。

DI:发送数据输入端,485通信过程中,始端信号发送引脚。

DE:发送数据使能,高电平有效,始端的485芯片DE引脚置1,进入发送模式。

A、B:信号传输线,准确的讲,只是起到导电作用。

VCC、GND:供电。

485通信为半双工通信,即同一时间只能接收或发送数据。而接收数据使能RE低电平有效,发送数据使能DE高电平有效,所以这两个引脚可以共接一个IO口。有以下情况:

①当IO口输出低电平时,接收数据使能RE有效,进入接收模式
②当IO口输出高电平时,发送数据使能DE有效,进入发送模式。

3、芯片测试

以SP485芯片为例,总结:

①发送模式时,DI输出“1”或“0”时传输线A、B上的电平情况

②接收模式时,传输线上不同电平时,RO引脚输出电平情况

STM32F103(二十四)一篇博客精通《485通信》_第3张图片

上图是我对SP485的测试结果,主设备用的F1板子,从设备为F4板子。再详述一下:

①当主设备设置为发送模式时,DI引脚发送“1”,则传输线上:A线高电平、B线低电平…同时从设备为接收模式,当A线为高电平、B线为低电平时,RO引脚输出高电平。

②当主设备设置为发送模式时,DI引脚发送“0”,则传输线上:A线低电平、B线高电平…同时从设备为接收模式,当A线为低电平、B线为高电平时,RO引脚输出低电平。

所以省略中间过程,我们知道:主设备发送“1”,从设备收到“1”,主设备发送“0”,从设备收到“0”。而485芯片没有时钟引脚,所以我们想到用异步串行传输USART。主设备USART一次输出8位的数,从设备一次接收8位的数,只需保证双方串口的波特率、停止位、校验位等一致即可

4、面向原理图

STM32F103(二十四)一篇博客精通《485通信》_第4张图片

我用的板子上使用的是串口2,USART_TX通过跳线帽与485的DI引脚连接;USART_RX通过跳线帽与485的RO引脚连接;PD7控制RE和DR,之前也总结到过,这里当PD7=0时为接收模式,当PD7=1时,为发送模式。

所以除了硬件连接,其他部分都是USART的代码

二、手把手写信号传递函数

通过第一部分总结,我们知道485通信可以通过双方的USART来输出和接收信号,所以USART收发函数就是485通信的函数,而485模块只需要设置为接收或发送模式,双方设置好后,其实就是两块板子之间的USART通信。

1、写一个发送一次u8数据的函数

void  My485_Send_One_Data(u8 data)
{
     
	PDout(7)=1;//发送数据使能
	while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
	USART_SendData(USART2,data);
}

上述代码中,首先PD7输出高电平,将485设置为发送数据模式。然后等待上一次数据发送完毕后,开启本次数据传输。代码比较简单。

2、写一个发送多个u8数据的函数

void  My485_Send_Len_data(u8 data[],u16 len)
{
     
	u16 t=0;
	for(t=0;t<len;t++)
	{
     
		My485_Send_One_Data(data[t]);
		delay_ms(10);//最好延迟,防止对方反应不过来
	}
}

这个函数也比较简单,data[]为传递的数据存储数组,len为需要传递的数据个数。其中My485_Send_One_Data(data[t]);函数是上面写的那个函数。

3、写一个接收多个数据函数的函数

发送数据的函数一般都比较好写,麻烦的是接收数据的函数,因为接收数据函数要用到中断,所以还需要配置中断。直接附已经调试好的代码,然后再讲思想:

void   My485_Receive_data(u8 len)
{
     
	NVIC_InitTypeDef  NVIC_InitStructure;
	PDout(7)=0;//接收数据使能
	NUM=0;
	RS485_Receive_Num=len;
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 
	
	USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}
extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据

void USART2_IRQHandler(void)
{
     
	u8 temp;
 	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
	{
     	 
	 	temp=USART_ReceiveData(USART2); 
		if(RS485_Receive_Num>NUM)
		{
     
			buffer[NUM]=temp;
			NUM++;
		}
		if(RS485_Receive_Num<=NUM)
			USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
	}  											 
} 

void My485_Receive_data(u8 len)函数中,将485模块设置为接收模式;将NUM这个全局变量清零,这个变量是用来统计目前已经接收到多少个数据。然后就是NVIC中断优先级设置和使能中断

这个接收函数配置好中断后,USART2就开始检测是否有数据输入了,只要有数据输入,就产生一次中断进入中断服务函数,然后接收到数据、存储数据、清除中断、等待下一次数据到来。当接收到我们需要接收到的数据个数时,再失能中断

数据存储的数组我采用的是全局数组,因为数据处理都是在中断服务函数中处理的,数组的地址没法传递。

void My485_Receive_data(u8 len)函数中可以再加一段程序,将储存数据的buffer[]数组清零,不过我觉得没必要,只需要保证通信不会出现异常,规定好接收数据的多少就可以了。

注意理解这个思想就好了,比如后面的例题,我把中断服务函数的以下代码放在了主函数中:

if(RS485_Receive_Num<=NUM)
			USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);

三、例题(F1板子发送——>F4板子接收)

1、题

F1板子发送一组1~100的100个数,通过双方485通信,F4接收到数据并通过串口打印在串口监视器上。

2、分析

F1板子发送数据,所以只用到上面写的第二个函数,需要配置USART2和485模块。

F4板子接收数据用到上面写的第三个函数,需要配置USART2和485模块。同时需要串口打印,所以需要配置USART1(我的两块板子都是:485与USART2连接,USART1通过RS232与USB连接)

3、F1板子代码(发送数据)

485.h代码:

#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void  My485_Send_One_Data(u8 data);
void  My485_Send_Len_data(u8 *,u16 );
void  My485_Receive_data(u8 len);//参数:读取数据个数
#endif

485.c代码:

#include "485.h"
#include "stm32f10x.h"
void My485_Init(u32 bound)
{
     
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	 NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);		//PA2  TX
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
	GPIO_Init(GPIOA, &GPIO_InitStructure);		//PA3  RX
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_Init(GPIOD, &GPIO_InitStructure);		//PD7 使能发送、接收
	
	USART_InitStructure.USART_BaudRate=bound;
	USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
	USART_InitStructure.USART_Parity=USART_Parity_No;
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_Init(USART2,&USART_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 
	
	USART_Cmd(USART2,ENABLE);
	
}

extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据

void USART2_IRQHandler(void)
{
     
	u8 temp;
 	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
	{
     	 
	 	temp=USART_ReceiveData(USART2); 
		if(RS485_Receive_Num>NUM)
		{
     
			buffer[NUM]=temp;
			NUM++;
		}
	}  											 
} 

void  My485_Send_One_Data(u8 data)
{
     
	PDout(7)=1;//发送数据使能
	while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
	USART_SendData(USART2,data);
}


void  My485_Send_Len_data(u8 data[],u16 len)
{
     
	u16 t=0;
	for(t=0;t<len;t++)
	My485_Send_One_Data(data[t]);
}


void  My485_Receive_data(u8 len)
{
     
	NVIC_InitTypeDef  NVIC_InitStructure;
	PDout(7)=0;//接收数据使能
	NUM=0;
	RS485_Receive_Num=len;
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 
	
	USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}

主函数代码:

#include "stm32f10x.h"
#include "485.h"
#include "delay.h"

u8 buffer[200];//接收的数据存储在这个数组
u8 RS485_Receive_Num=0;//储存需要接收的数据个数
u8 NUM=0; //统计现在接收到几个数据
u8 arr[200];  //发送的数据储存在这个数组

int main(void)
 {
     	
	 u16 i;
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	 delay_init();
	 My485_Init(115200);
	 for(i=0;i<100;i++)
		arr[i]=i+1;//赋初值
	 
	 delay_ms(1500);
	 My485_Send_Len_data(arr,100);
	 
	 while(1)
	 {
     	
	 } 
 }

主函数写的是按下复位后,隔1.5后发送100个数据

4、F4板子代码(接收数据并串口打印)

USART1头文件代码(为了串口打印):

 #ifndef _USART_
#define _USART_
#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"
void Myusart1_Init(u32);
#endif

USART的.c文件代码(为了串口打印):

#include "myusart.h"
#include "stm32f4xx.h"
#include "sys.h"
#include "string.h"

void Myusart1_Init(u32 baud)
{
     
	 GPIO_InitTypeDef  GPIO_InitStructure;
	 USART_InitTypeDef  USART_InitStructure;
	 NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); 
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); 
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
	
	USART_InitStructure.USART_BaudRate=baud;
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStructure.USART_Parity=USART_Parity_No;
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1,&USART_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 

	USART_Cmd(USART1, ENABLE);
}

485.h代码:

#ifndef _485_
#define _485_
#include "sys.h"
void My485_Init(u32 bound);
void  My485_Send_One_Data(u8 data);
void  My485_Send_Len_data(u8 *,u16 );
void  My485_Receive_data(u8 len);//参数:读取数据个数
#endif

485.c代码:

#include "485.h"
#include "stm32f4xx.h"

void My485_Init(u32 bound)
{
     
	 GPIO_InitTypeDef  GPIO_InitStructure;
	 USART_InitTypeDef  USART_InitStructure;
	
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); 
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); 
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); 
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOG,&GPIO_InitStructure);    //PG8 发送接收使能
	
	USART_InitStructure.USART_BaudRate=bound;
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStructure.USART_Parity=USART_Parity_No;
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART2,&USART_InitStructure);

	USART_Cmd(USART2, ENABLE);
	
}

extern u8 buffer[200];//接收的数据存储在这个数组
extern u8 RS485_Receive_Num;//储存需要接收的数据个数
extern u8 NUM; //统计现在接收到几个数据

void USART2_IRQHandler(void)
{
     
	u8 temp;
 	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
	{
     	 
	 	temp=USART_ReceiveData(USART2); 
		if(RS485_Receive_Num>NUM)
		{
     
			buffer[NUM]=temp;
			NUM++;
		}
	}  											 
} 

void  My485_Send_One_Data(u8 data)
{
     
	PGout(8)=1;//发送数据使能
	while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待上一次数据发送完成
	USART_SendData(USART2,data);
}


void  My485_Send_Len_data(u8 data[],u16 len)
{
     
	u16 t=0;
	for(t=0;t<len;t++)
	My485_Send_One_Data(data[t]);
}


void  My485_Receive_data(u8 len)
{
     
	NVIC_InitTypeDef  NVIC_InitStructure;
	PGout(8)=0;//接收数据使能
	NUM=0;
	RS485_Receive_Num=len;
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 
	
	USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
}

main.c代码:

#include "stm32f4xx.h"
#include "myusart.h"
#include "delay.h"
#include "485.h"

u8 buffer[200];//接收的数据存储在这个数组
u8 RS485_Receive_Num=0;//储存需要接收的数据个数
u8 NUM=0; //统计现在接收到几个数据
u8 arr[200];  //发送的数据储存在这个数组

int main(void)
{
     

	u16 i;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	Myusart1_Init(115200);
	My485_Init(115200);
	delay_init(168);
	
	My485_Receive_data(100);//接收100个数据
	while(1)
	{
     
			if(RS485_Receive_Num<=NUM)
			{
     
				NUM=0;
				USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
				for(i=0;i<RS485_Receive_Num;i++)
					{
     
						USART_SendData(USART1,buffer[i]);//通过串口监视器显示
						delay_ms(10);//一定要延时,保证每个数据能准确发送到串口
					}
			}
			
//	USART_SendData(USART1,buffer[45]);//通过串口监视器显示
	}	
}

5、引脚连接

①F1板子485端子A线与F4板子485端子A线相连,B线与B线相连。

②保证F1板子与F4板的USART2与485模块相连。

③保证F4板子USART1与USB输出相连。

6、测试

USB连接F4板子并打开串口监视器。同时复位F1、F4板子,察看数据接收情况:
STM32F103(二十四)一篇博客精通《485通信》_第5张图片

四、总结

485通信,主要还是用到上一篇博客总结的USART通信。双方485模块只是提供了长途导线连接接口。明白了USART通信和485模块的作用,485通信应该很好理解的。

当然,对于大型项目,32的CPU无法满足计算要求,可以利用485通信在上位机中处理数据:
STM32F103(二十四)一篇博客精通《485通信》_第6张图片

我用的485模块与上位机的连接器:
STM32F103(二十四)一篇博客精通《485通信》_第7张图片

感兴趣的朋友可以去尝试一下,上位机代码是VS用C#编的。

你可能感兴趣的:(STM32F107,stm32,单片机,物联网)