1. 程式人生 > >MODBUS主機協議移植

MODBUS主機協議移植

      最近由於專案需求需要移植的modbus主機協議,由於MODBUS主機的原始碼不開源,網上的例程也不多,在百度苦苦找尋一番沒有什麼結果。最後參考了安富萊的例程,將其修改並移植到自己的工程中。我通過USB轉串列埠連線到電腦,用虛擬裝置測試,讀寫正常。以下是我的移植過程。下面有stm32F1和msp430F5的原始碼與MODBUS虛擬裝置

下面使用的晶片是stn32f103zet6,IDE為MDK

       1,首先將下面幾個檔案新增到工程中

 

 

 

 

 

 

 

 

 

 

 

 

2,接下來只需要新增幾個介面函式就能正常執行

//需要連線的外部介面函式
extern void (*RS485_ReceiveData)(uint8_t);	//接受資料,在串列埠中斷接受到的數傳遞到此函式即可
extern void RS485_SendBuf(uint8_t *_ucaBuf, uint16_t _usLen);	//傳送資料

extern void bsp_StartHardTimer(uint32_t timeout, void * _pCallBack);	//開始計時3.5個位元組的時間,單位為us
//以下兩個函式用來測量間隔時間
extern uint32_t bsp_GetRunTime(void);	//返回當前時間,單位ms
extern uint32_t bsp_CheckRunTime(int32_t _LastTime);		//返回與之前間隔的時間

這兩個函式由於需要使用到串列埠所以需要先初始化串列埠

extern void (*RS485_ReceiveData)(uint8_t);	//接受資料,在串列埠中斷接受到的數傳遞到此函式即可
extern void RS485_SendBuf(uint8_t *_ucaBuf, uint16_t _usLen);	//傳送資料

下面是串列埠初始化函式,這裡使用串列埠3

    myusart.c檔案下

void MODBUS_USART3_Init(u32 bound)
{
	//GPIO埠設定
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);	//使能USART1,GPIOA時鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//USART1_TX   GPIOB.10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//複用推輓輸出
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO1.10
   
	//USART1_RX	  GPIOB.11初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.11  

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶佔優先順序3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		//子優先順序2
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根據指定的引數初始化VIC暫存器
  
   //USART 初始化設定

	USART_InitStructure.USART_BaudRate = bound;//串列埠波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位資料格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體資料流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收發模式

	USART_Init(USART3, &USART_InitStructure); //初始化串列埠1
	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//開啟串列埠接受中斷
	USART_Cmd(USART3, ENABLE);                    //使能串列埠1 
	
}

串列埠初始化成功後,可以新增上面那幾個介面函式

        串列埠3中斷中新增RS485_ReceiDate()函式指標

void USART3_IRQHandler(void)                	//串列埠3中斷服務程式,modbus串列埠接受介面
{
	uint8_t ch;
	if(USART_GetITStatus(USART3,USART_IT_RXNE) == SET)
	{
		ch = USART_ReceiveData(USART3);
		RS485_ReceiveData(ch);
	}
	USART_ClearITPendingBit(USART3,USART_IT_RXNE);
} 

 新增函式void RS485_SendBuf(uint8_t * _ucaBuf,uint16_t _usLen)

void RS485_SendBuf(uint8_t *_ucaBuf, uint16_t _usLen)
{
	uint16_t num;
	for(num = 0;num<_usLen;num++)
	{
		while(USART_GetFlagStatus(USART3,USART_FLAG_TXE) == RESET);
		USART_SendData(USART3,*_ucaBuf);
		_ucaBuf++;
	}	
}

串列埠部分已經配置完成,接下來是定時器的部分

需要配置以下幾個函式

extern void bsp_StartHardTimer(uint32_t timeout, void * _pCallBack);	//開始計時3.5個位元組的時間,單位為us
//以下兩個函式用來測量間隔時間
extern uint32_t bsp_GetRunTime(void);	//返回當前時間,單位ms
extern uint32_t bsp_CheckRunTime(int32_t _LastTime);		//返回與之前間隔的時間

同樣的需要先初始化定時器,這裡使用定時器2和定時器3

   在/timer.c裡檔案下

void MODBUS_Tim2_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	uint32_t usPeriod;
	uint16_t usPrescaler;
	uint32_t uiTIMxCLK;

  	/* 使能TIM時鐘 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	uiTIMxCLK = SystemCoreClock / 2;

	usPrescaler = uiTIMxCLK / 1000000 ;	/* 分頻到週期 1us */
	
	usPeriod = 0xFFFF;	/* 103支援16位 */
	
	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = usPeriod;
	TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

	/* TIMx enable counter */
	TIM_Cmd(TIM2, DISABLE);

	/* 配置TIM定時中斷 (Update) */
	{
		NVIC_InitTypeDef NVIC_InitStructure;	/* 中斷結構體在 misc.h 中定義 */

		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;

		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;	/* 比串列埠優先順序低 */
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
	}
}

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_CC3) == SET)
    {
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
        TIM_ITConfig(TIM2, TIM_IT_CC3, DISABLE);	/* 禁能CC3中斷 */
		
        s_TIM_CallBack();	//將標誌位置1
		
		TIM_Cmd(TIM2, DISABLE);
    }
}
void MODBUS_Tim3_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

  	/* 使能TIM時鐘 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	
	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = 999;
	TIM_TimeBaseStructure.TIM_Prescaler = 35;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

	/* TIMx enable counter */
	TIM_Cmd(TIM3, ENABLE);

	/* 配置TIM定時中斷 (Update) */
	{
		NVIC_InitTypeDef NVIC_InitStructure;	/* 中斷結構體在 misc.h 中定義 */

		NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;

		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;	/* 比串列埠優先順序低 */
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
	}
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
}

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
    {
		g_iRunTime++;
		if(g_iRunTime > 0x7FFFFFFF) g_iRunTime = 0;
    }    
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}

定時器2用來檢測3.5個位元組的時間來判斷一幀資料是夠結束

定時器3用於計數,只要要1ms的記一次數就可以

接下來是void bsp_StartHardTimer(uint32_t _uiTimeOut,void * _pCallBack)函式

在這個函式中開啟中斷,在3.5個位元組的時間後會進入中斷,具體時間要根據波特率設定

void bsp_StartHardTimer(uint32_t _uiTimeOut, void * _pCallBack)
{
	uint32_t cnt_now;
    uint32_t cnt_tar;

    if (_uiTimeOut < 5)
    {
        ;
    }
    else
    {
        _uiTimeOut -= 5;
    }
	TIM_SetCounter(TIM2,0);
	TIM_Cmd(TIM2, ENABLE);
	
    cnt_now = TIM_GetCounter(TIM2);    	/* 讀取當前的計數器值 */
    cnt_tar = cnt_now + _uiTimeOut;			/* 計算捕獲的計數器值 */
    
    s_TIM_CallBack = (void (*)(void))_pCallBack;

    TIM_SetCompare3(TIM2, cnt_tar);      	/* 設定捕獲比較計數器CC3 */
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
	TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE);	/* 使能CC3中斷 */
	
}

下面是uint32_t bsp_GetRunTime(void)與uint32_t bsp_CheckRunTime(int32_t _LastTime)

這兩個函式是用來測量時間,判斷是否通訊超時

uint32_t bsp_GetRunTime(void)
{
	int32_t runtime;

	__set_PRIMASK(1);  	/* 關中斷 */

	runtime = g_iRunTime;	/* 這個變數在Systick中斷中被改寫,因此需要關中斷進行保護 */

	__set_PRIMASK(0);  		/* 開中斷 */

	return runtime;
}

//獲取間隔時間
uint32_t bsp_CheckRunTime(int32_t _LastTime)
{
	int32_t now_time;
	int32_t time_diff;

	__set_PRIMASK(1);   	/* 關中斷 */

	now_time = g_iRunTime;	/* 這個變數在Systick中斷中被改寫,因此需要關中斷進行保護 */

	__set_PRIMASK(0);   		/* 開中斷 */
	
	if (now_time >= _LastTime)
	{
		time_diff = now_time - _LastTime;
	}
	else
	{
		time_diff = 0x7FFFFFFF - _LastTime + now_time;
	}

	return time_diff;
}

到這裡已經基本上移植完成,接下來就是測試

呼叫一下這些函式看看是否移植成功

uint8_t MODH_ReadParam_01H(uint16_t slaveAddr,uint16_t _reg, uint16_t _num);	//讀線圈暫存器
uint8_t MODH_ReadParam_02H(uint16_t slaveAddr,uint16_t _reg, uint16_t _num);	//讀離散輸入暫存器
uint8_t MODH_ReadParam_03H(uint16_t slaveAddr,uint16_t _reg, uint16_t _num);	//讀儲存暫存器
uint8_t MODH_ReadParam_04H(uint16_t slaveAddr,uint16_t _reg, uint16_t _num);	//讀輸入暫存器
uint8_t MODH_WriteParam_05H(uint16_t slaveAddr,uint16_t _reg, uint16_t _value);	//寫單個線圈暫存器
uint8_t MODH_WriteParam_06H(uint16_t slaveAddr,uint16_t _reg, uint16_t _value);	//寫單個儲存暫存器	
uint8_t MODH_WriteParam_10H(uint16_t slaveAddr,uint16_t _reg, uint8_t _num, const uint16_t *_buf);	//寫多個儲存暫存器

3,測試

首先將硬體連線好,我這裡是用虛擬裝置,將32的串列埠3通過串列埠轉USB連線到電腦

 

開啟虛擬裝置匯入暫存器,設定好串列埠與波特率

 

在主函式中呼叫讀寫函式,若讀寫成功則返回1,否則返回0

觀察虛擬機器現象並通過串列埠1列印測試結果

 在主函式中

		if(MODH_WriteParam_06H(1,4,9))
		{
			printf("寫入成功06\r\n");
		}
		else
		{
			printf("通訊失敗06\r\n");
		}
		if(MODH_WriteParam_10H(1,0, 3,buff))
		{
			printf("寫入成功10\r\n");
		}
		else
		{
			printf("通訊失敗10\r\n");
		}
		if(MODH_ReadParam_02H(1,0,10))
		{
			printf("讀取成功02\r\n");
		}
		else
		{
			printf("通訊失敗02\r\n");
		}		

到這裡我的整個移植過程就結束了

下面是原始碼和除錯用的虛擬裝置。

 

STM32原始碼

 

MSP430原始碼