天天看點

基于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

繼續閱讀