1. 程式人生 > 其它 >FreeMODBUS庫 實現串列埠的選擇切換

FreeMODBUS庫 實現串列埠的選擇切換

技術標籤:嵌入式modbusfreertos

FreeMODBUS庫 實現串列埠的選擇切換

日期作者版本說明
2020.12.16HxjV1.0完成主體

目錄


介紹

在FreeMODBUS 庫中是不支援串列埠在執行的時候進行切換,同時也不支援多串列埠同時執行,必須得從新的配置才能進行串列埠的輸出的選擇。這樣就使得程式有他的侷限性。下文將會著重介紹如何進行選擇性配置串列埠的輸出。


一、FreeMODEBUS的串列埠配置

原始碼

標頭檔案

/*
 * mb_user.h
 *
 *  Created on: May 24, 2020
 *      Author: 81001
 */
#ifndef FREEMODBUS_USER_MB_USER_H_ #define FREEMODBUS_USER_MB_USER_H_ #include "mb_user.h" #include "mb.h" #include "mbplatform.h" #include "mb_coilsreg.h" #include "mb_discretereg.h" #include "mb_inputreg.h" #include "mb_holdingreg.h"
#include "gpio_ext.h" //是否啟用FreeModbus模組 #define FREEMODBUS_ENABLE 1 //選擇PORT型別是否為RS485模組 #define FREEMODBUS_PORT_INTERFACE_RS485 //#define FREEMODBUS_PORT_INTERFACE_RS232 //設定FreeModbus從機的地址 #define FREEMODBUS_DEV_ADDR 0x01 #define RS485_T_CTRL GPIOE_OUT(15) #define FREEMODBUS_TIMER 2 extern
uint8_t FreeModbus_PortNum; extern USART_TypeDef *USART_Modbus; extern uint32_t FreeModbus_PortBaudrate; extern eMBParity Freemodbus_PortParity; extern uint8_t DipValue; extern uint8_t PORT_Switch; void User_MB_InitPortParam(uint8_t portNum, uint32_t baudRate, eMBParity parity); void User_MB_Config(); void User_MB_ConfigNVIC(); void User_MB_InitRegs(); void User_MB_RefreshInputRegister(); void User_MB_RefreshCoilsRegister(); void User_MB_RefreshDiscreteRegister(); void User_MB_RefreshHoldingRegister(); #endif /* FREEMODBUS_USER_MB_USER_H_ */

原始檔

/*
 * mb_user.c
 *
 *  Created on: May 24, 2020
 *      Author: 81001
 */
#include "mb_user.h"
#include "sensor.h"
#include "user.h"
#include "tc.h"
#include "globalVariate.h"
#include "usart_ext.h"
#include "bldcmotor.h"
uint8_t FreeModbus_PortNum = 3;
USART_TypeDef *USART_Modbus = USART3;
uint32_t FreeModbus_PortBaudrate = 115200;
eMBParity Freemodbus_PortParity = MB_PAR_NONE;
uint8_t PORT_Switch = 0;
/**
 * @brief 當freemodbus中出錯時,呼叫此函式進行錯誤處理
 * @param file: 一般情況下,填入出錯所在的檔案 __FILE__
 * @param line: 一般情況下,填入出錯所在的行 __LINE__
 * @param function: 一般情況下,填入__ASSERT_FUNC
 * @param error: freemodbus庫中傳入出錯引數(直接轉化為字串)
 */
void __attribute__((weak)) User_AssertFunc(const char *file, int line,
		const char *function, const char *error)
		{
	/*not realize yet*/
		}

/**
 * @brief 初始化modbus的通訊串列埠引數
 * 		此函式並沒有真正的配置通訊埠,而是初始化modbus通訊埠引數的幾個全域性變數。
 * 		設計這些全域性變數的主要原因是,在整個專案工程中還涉及其他地方需要用到這些引數,
 * 		例如串列埠的中斷服務函式中需要根據本串列埠有無被佔用為modbus通訊埠而切換相應的服務內容,
 * 		NVIC與外設配置也依賴這幾個變數的值實現通訊埠外設的自動配置。
 * @param portNum: 串列埠號,1~5 對應 USART1~USART5外設
 * @param baudRate: 波特率
 * @param parity: 校驗,列舉變數
 * 		@param MB_PAR_NONE: 無校驗
 * 		@param MB_PAR_ODD: 奇校驗
 * 		@param MB_PAR_EVEN: 偶校驗
 */
void __attribute__((weak)) User_MB_InitPortParam(uint8_t portNum,
		uint32_t baudRate, eMBParity parity)
		{
	FreeModbus_PortNum = portNum;
	FreeModbus_PortBaudrate = baudRate;
	Freemodbus_PortParity = parity;

	switch (portNum)
	{
	case 1:
		USART_Modbus = USART1;
		break;
	case 2:
		USART_Modbus = USART2;
		break;
	case 3:
		USART_Modbus = USART3;
		break;
#if defined(STM32F10X_HD) || defined(STM32F10X_HD_VL) || defined(STM32F10X_XL) || defined(STM32F10X_CL)
	case 4:
		USART_Modbus = UART4;
		break;
	case 5:
		USART_Modbus = UART5;
		break;
#endif
	default:
		break;
	}
		}
/**
 * @brief 提供了一個快速配置並啟動Modbus功能的模板(採用預設配置)
 * 		需要注意的是為了使此函式儘可能的簡潔易用,並未設定函式引數。
 * 		使用者需要根據自己專案的實際情況修改函式中的各個配置引數。
 */
void User_MB_Config()
{
	switch (BitData_PortCtrl.ByteData)
	{
	//Bit0 = 0, configure DB-9 port
	case 0:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_DB9_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_DB9_TXD_PORT, USART_DB9_TXD_PIN, USART_DB9_RXD_PORT, USART_DB9_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;

		//Bit0 = 1, configure LCD port
	case 1:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_LCD_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_LCD_TXD_PORT, USART_LCD_TXD_PIN, USART_LCD_RXD_PORT, USART_LCD_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;
		//Bit0 = 0 & Bit1 = 1, configure RS485 port
	case 2:
		//Bit0 = 1 & Bit1 = 1, configure RS485 port
	case 3:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_485_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_485_TXD_PORT, USART_485_TXD_PIN, USART_485_RXD_PORT, USART_485_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;

	default:
		break;
	}
	//初始化Modbus四種暫存器
	User_MB_InitRegs();
	//初始化Modbus
	eMBInit(MB_RTU, FREEMODBUS_DEV_ADDR, FreeModbus_PortNum,
			FreeModbus_PortBaudrate, Freemodbus_PortParity);
	//使能Modbus功能
	eMBEnable();
}

/**
 * @brief 配置FreeModbus相關的中斷
 * 		主要是配置時基定時器的中斷與串列埠收發中斷
 * 		預設選擇NVIC_PriorityGroup_2中斷優先順序分組
 */
void __attribute__((weak)) User_MB_ConfigNVIC()
{
	NVIC_InitTypeDef NVIC_InitStructure;
	//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	switch (FreeModbus_PortNum)
	{
	case 1:
		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
		break;
	case 2:
		NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
		break;
	case 3:
		NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
		break;
#if defined(STM32F10X_HD) || defined(STM32F10X_HD_VL) || defined(STM32F10X_XL) || defined(STM32F10X_CL)
	case 4:
		NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
		break;
	case 5:
		NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
		break;
#endif
	default:
		break;
	}

	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	switch (FREEMODBUS_TIMER)
	{
	case 1:
		NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
		break;
	case 2:
		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
		break;
	case 3:
		NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
		break;
	case 4:
		NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
		break;
	default:
		break;
	}

	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

/**
 * @brief 完成各個暫存器的初始化
 */
void User_MB_InitRegs()
{
	RegHoldingBuf_Init();
	RegDiscreteBuf_Init();
	RegCoilsBuf_Init();
	RegInputBuf_Init();
}

/**
 * @brief 重新整理輸入暫存器,將感測器資料賦值到輸入暫存器
 * @param none
 * @retval none
 */
void User_MB_RefreshInputRegister()
{
}
/**
 * @brief 重新整理線圈暫存器,根據線圈暫存器的值執行對應動作
 * @param none
 * @retval none
 */
void User_MB_RefreshCoilsRegister()
{
	//設定緩衝陣列,提高輪詢效率,只有緩衝陣列中的資料發生變化的時候,才會實際暫存器的讀寫操作
	static uint8_t coilsReg_buffer[32] =
	{ 2 };

	(void) coilsReg_buffer;

	//開啟串列埠切換
	if (coilsReg_buffer[24] != CoilsReg_GetBit(24))
	{
		coilsReg_buffer[24] = CoilsReg_GetBit(24);
		BitData_PortCtrl.BitData.bit1 = coilsReg_buffer[24];
	}
}
/**
 * @brief Refresh discrete registers
 */
void User_MB_RefreshDiscreteRegister()
{
	//設定緩衝陣列,提高輪詢效率,只有緩衝陣列中的資料發生變化的時候,才會實際暫存器的讀寫操作
	static uint8_t discreteReg_buffer[10] =
	{ 99 };
	(void) discreteReg_buffer;
}
/**
 * @brief Refresh holding registers
 */
void User_MB_RefreshHoldingRegister()
{

}

1.串列埠的選擇配置函式 void User_MB_Config()解析
BitData_PortCtrl.ByteDataBitData_PortCtrl.BitData共用一塊記憶體 所以可以直接用BitData_PortCtrl.ByteData進行判斷。

/**
 * @brief 提供了一個快速配置並啟動Modbus功能的模板(採用預設配置)
 * 		需要注意的是為了使此函式儘可能的簡潔易用,並未設定函式引數。
 * 		使用者需要根據自己專案的實際情況修改函式中的各個配置引數。
 */
void User_MB_Config()
{
	switch (BitData_PortCtrl.ByteData)
	{
	//Bit0 = 0, configure DB-9 port
	case 0:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_DB9_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_DB9_TXD_PORT, USART_DB9_TXD_PIN, USART_DB9_RXD_PORT, USART_DB9_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;

		//Bit0 = 1, configure LCD port
	case 1:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_LCD_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_LCD_TXD_PORT, USART_LCD_TXD_PIN, USART_LCD_RXD_PORT, USART_LCD_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;

		//Bit0 = 0 & Bit1 = 1, configure RS485 port
	case 2:
		//Bit0 = 1 & Bit1 = 1, configure RS485 port
	case 3:
		//配置Modbus通訊串列埠的全域性引數
		User_MB_InitPortParam(USART_485_NUM, 115200, MB_PAR_NONE);
		//配置GPIO
		USART_ConfigGPIO(USART_485_TXD_PORT, USART_485_TXD_PIN, USART_485_RXD_PORT, USART_485_RXD_PIN);
		//配置NVIC
		User_MB_ConfigNVIC();
		break;

	default:
		break;
	}

	//初始化Modbus四種暫存器
	User_MB_InitRegs();
	//初始化Modbus
	eMBInit(MB_RTU, FREEMODBUS_DEV_ADDR, FreeModbus_PortNum,
			FreeModbus_PortBaudrate, Freemodbus_PortParity);
	//使能Modbus功能
	eMBEnable();
}

二.使用指南

思路說明:

本文實現的是通過使用硬體(撥碼開關)來實現主串列埠的選擇(DB9 或 LCD),主次串列埠的切換是通過一位線圈暫存器控制通過 寫入0 1進行主次串列埠的切換。
串列埠的配置通過呼叫 void User_MB_Config()

使用步驟

1.在程式初始化階段 通過一個位域操作 建立一個類似位操作的共用體,通過最小的空間來進行最多的操作。

typedef struct
{
	uint8_t bit0:1;
	uint8_t bit1:1;
	uint8_t bit2:1;
	uint8_t bit3:1;
	uint8_t bit4:1;
	uint8_t bit5:1;
	uint8_t bit6:1;
	uint8_t bit7:1;
} BitDataStruct;

typedef union
{
	BitDataStruct BitData;
	uint8_t ByteData;
} BitDataUnion;

2.在進入任務前 獲取撥碼開關的值將其儲存在BitData_PortCtrl.BitData.bit0

	DipValue = User_GetDipSwitchValue();//讀取撥碼開關的值

	BitData_PortCtrl.BitData.bit0 = (DipValue & 0x01);//將其儲存

//OFF==0   ON ==1
uint8_t User_GetDipSwitchValue()
{
		uint8_t value = 0x00;
		uint8_t dipSwitchBit1 = (~(*DIP_SWITCH_BIT_1)) & 0x01;
		uint8_t dipSwitchBit2 = (~(*DIP_SWITCH_BIT_2)) & 0x01;
		uint8_t dipSwitchBit3 = (~(*DIP_SWITCH_BIT_3)) & 0x01;
		uint8_t dipSwitchBit4 = (~(*DIP_SWITCH_BIT_4)) & 0x01;
		value = ((dipSwitchBit1 << 0) | (dipSwitchBit2 << 1) | (dipSwitchBit3 << 2) | (dipSwitchBit4 << 3)) & 0x0F;
		return value;
}

3.在FreeRTOS系統中建立一個任務進行串列埠的選擇配置並進行輪詢Modbus。

void ModbusPoll_task(void *pvParameters)
{
	User_MB_Config();//串列埠的初始化配置
	TickType_t ticks = xTaskGetTickCount();
	while (1)
	{
		eMBPoll();// modebus 的輪詢
		//1ms 一個處理週期
		vTaskDelayUntil( &ticks, 1 );
	}
}

4.在重新整理線圈暫存器中 輪詢串列埠切換控制位是否發生改變。改變後將值賦值給BitData_PortCtrl.BitData.bit1

/**
 * @brief 重新整理線圈暫存器,根據線圈暫存器的值執行對應動作
 * @param none
 * @retval none
 */
void User_MB_RefreshCoilsRegister()
{
	//設定緩衝陣列,提高輪詢效率,只有緩衝陣列中的資料發生變化的時候,才會實際暫存器的讀寫操作
	static uint8_t coilsReg_buffer[32] =
	{ 2 };
	(void) coilsReg_buffer;
	//開啟串列埠切換
	if (coilsReg_buffer[24] != CoilsReg_GetBit(24))
	{
		coilsReg_buffer[24] = CoilsReg_GetBit(24);
		BitData_PortCtrl.BitData.bit1 = coilsReg_buffer[24];
	}
}

5.在FreeRTOS系統中建立一個串列埠切換任務。輪詢BitData_PortCtrl.BitData.bit1是否發生變化。改變後先將void ModbusPoll_task(void *pvParameters)任務刪除 之後再進行建立新的void ModbusPoll_task(void *pvParameters)任務這樣配置函式 void User_MB_Config()將進行重新的選擇。

void  PortSwitch_task(void *pvParameters)
{
	static uint8_t Port=0;
	TickType_t ticks = xTaskGetTickCount();
	while(1)
	{
		if(Port != BitData_PortCtrl.BitData.bit1)
		{
			Port = BitData_PortCtrl.BitData.bit1;

			taskENTER_CRITICAL();			//進入臨界區

			eMBDisable();
			vTaskDelete(ModbusPollTask_Handler); //刪除ModbusPollTask_Handler任務

			xTaskCreate((TaskFunction_t )ModbusPoll_task,
					(const char*    )"ModbusPoll_task",
					(uint16_t       )MODBUS_POLL_STK_SIZE,
					(void*          )NULL,
					(UBaseType_t    )MODBUS_POLL_TASK_PRIO,
					(TaskHandle_t*  )&ModbusPollTask_Handler);

			taskEXIT_CRITICAL();				//退出臨界區
		}
		vTaskDelayUntil( &ticks, 500);
	}
}