天天看点

基于STM32的MODBUS-RS485——RTU程序实现

这里为什么说RS485是MODBUS的实现载体呢,因为RS485仅仅只是一种硬件电平标准,我的理解就是在串口的基础上加了电平转换芯片比如MAX485,将串口的TTL电平转换成了RS485差分电平。

贴上代码段

1.RS485——串口2初始化

void RS485_USART2_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	USART_InitTypeDef  USART_InitStruct;
	NVIC_InitTypeDef  NVIC_InitStruct;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
	 //GPIOA2,A3初始化设置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//复用模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
	



//串口初始化配置
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_Cmd(USART2,ENABLE);

USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);



NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
GPIO_ResetBits(GPIOG,GPIO_Pin_8);//初始化为接收状态
}
           

因为需要控制芯片引脚收发,所以要利用PG8来控制接收或者发送使能,具体的硬件原理图于正点原子F407的相同,大家可以参考那个。

定时器初始化

void Timer2_Init(void)//84M
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruc;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	TIM_TimeBaseInitStruc.TIM_Period=1000-1;//1ms
	TIM_TimeBaseInitStruc.TIM_Prescaler=84-1;//84M/84=1MHZ->1us
	TIM_TimeBaseInitStruc.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStruc.TIM_CounterMode=TIM_CounterMode_Up;	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruc);
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);	
	TIM_Cmd(TIM2,ENABLE);
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
}
void Timer2_NVIC_Configuration(void)
{
	NVIC_InitTypeDef  NVIC_InitStruct;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	
	NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
	NVIC_Init(&NVIC_InitStruct);
}
           

发送字节与发送多个字节函数

void RS485_SendByte(u8 bbyte)
{
	RS_485_TX;
	USART_SendData(USART2, bbyte);
	while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
	RS_485_RX;
}
void RS485_SendData(u8 *Sdbuf,u8 len)
{
	u8 i;
	RS_485_TX;
	for(i=0;i<len;i++)
	{
		USART_SendData(USART2,Sdbuf[i]);
		while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
	}
	while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
	RS_485_RX;	
}
           

我认为这些都是基础知识,大家应该首先根据正点原子把能够利用485收发全部实现,然后再这个基础上加入485协议。

接下来就是串口的接收以及定时器中断函数,modbus的复杂主要就麻烦在如何将串口的接收中断与定时器中断配合好,达到完整的接收一帧数据,并且不影响下一帧接受的效果。

void USART2_IRQHandler()
{
	u8 res;
	if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
	{
		res =USART_ReceiveData(USART2);
		modbus.ReceiveBuff[modbus.ReceiveCount]=res;
		modbus.ReceiveCount++;
		if(modbus.ReceiveCount==1)
		{
			modbus.timerun=1;
		}
		modbus.timecount=0;
	}
}
void TIM2_IRQHandler()
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		if(modbus.timerun==1)
		{
			modbus.timecount++;
		}
		if(modbus.timecount>=5)
		{
			modbus.timerun=0;
			modbus.timecount=0;
			modbus.ReceiveComplete=1;
		}
	}
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
           

在这里为了方便,定义了一个modbus的结构体

typedef struct
{
	u8 Slave_ID;			//从机ID
	u8 ReceiveBuff[20];		//接收缓存数组
	u8 ReceiveCount;		//计算接收到的数据有多少字节
	u8 timecount;			//有多久没有接收到字节,数据断续的时间
	u8 timerun;				//断续时间是否开始累加
	u8 ReceiveComplete;		//一帧数据接收完成标志	
}MODBUS;
           

modbus协议主要就是,在接收时有3.5个字符间隔,那么就认为这一帧数据接收结束,可以进行处理并且接收下一帧数据。

在这里定义了一个接收缓存数组,用于将串口2接收到的数据放到缓存数组中。

大概逻辑是这样的,首先主机发送命令后,从机返回一帧数据,当接收到一帧数据的第一个字符时,计时器开始计时,timrun启动,timcount++,如果在3.5个字符内又收到了下一个字节数据,那么timcount就会置0,使他无法到达3.5个字符间隔,如果接收到了,就将数据继续放入到数组中。直到接收完毕,定时器到达3,5个字符间隔,此时接收完成ReceiveComplete=1;然后这个时候为了防止下一帧数据进来把数据覆盖掉,需要把接收缓存数组ReceiveBuff中的数据copy出去,

copy数据的函数

void RS485_Receive_Data(u8 *buf,u8 *len)
{
	u8 i;
	u8 Temp_len;
	Temp_len=modbus.ReceiveCount;
	if(modbus.ReceiveComplete==1)
	{
		for(i=0;i<Temp_len;i++)
		{
			buf[i]=modbus.ReceiveBuff[i];
		}
		*len=modbus.ReceiveCount;
		modbus.ReceiveCount=0;	
	}
	modbus.ReceiveComplete=0;
	
}
           

copy完后主程序处理数据,中断继续接收。

总的程序在这里。有积分的话支持一下,没积分的话评论区留下邮箱,我抽空发你。毕竟自己学习新东西也需要积分。算是形成一个良性循环吧。

程序连接https://download.csdn.net/download/zwbzero/19266609

用到的调试软件链接https://download.csdn.net/download/zwbzero/19066229

继续阅读