【三】基於open62541的STM32平台OPCUA服務器搭建


以下內容大致於 2020 年 9 月左右在 stm32 平台再次編譯完成,但是沒有 發布出來,本文中涉及到的代碼可能已經發生變化,請以官方代碼倉庫為准

准備 FreeRTOS+LwIP

stm32f4

前面編譯單文件的就說,open62541 是建立在系統上的,所以在移植前,需要准備一份運行良好的 STM32 FressRTO+Lwip 的代碼,沒有的可以在 GitHub 上STM32F4-FreeRTOS-LwIP下載,代碼具體信息在 readme.md 中有說。編譯 stm32 代碼的 IDE 根據個人喜好,我使用的是 keil 5。

image-20200824102457052
當 FreeRTOS+LwIP 沒問題之后就可以開始下一步了。

編譯條件

添加宏定義

  1. 在 FreeRTOS+LWIP 項目中添加架構宏——UA_ARCHITECTURE_FREERTOSLWIP
    image-20200901153014645
  2. 添加 LwIP 宏
    // 在`lwipopts.h`文件添加如下內容
    
    #define LWIP_COMPAT_SOCKETS 0 // Don't do name define-transformation in networking function names.
    #define LWIP_SOCKET 1 // Enable Socket API (normally already set)
    #define LWIP_DNS 1 // enable the lwip_getaddrinfo function, struct addrinfo and more.
    #define SO_REUSE 1 // Allows to set the socket as reusable
    #define LWIP_TIMEVAL_PRIVATE 0 // This is optional. Set this flag if you get a compilation error about redefinition of struct timeval
    
  3. 添加 FreeRTOS 宏
    // 在`FreeRTOSConfig.h`文件添加如下內容
    
    #define configCHECK_FOR_STACK_OVERFLOW 1
    #define configUSE_MALLOC_FAILED_HOOK 1
    

注釋

注釋掉 sockets.h 文件的 315~319 行、324 行等

注釋前:

image-20200901162208345

注釋后:

image-20200901162235602

這個是因為 open62541 中用到了這個結構體,但是原本的 LwIP 沒有用的,所以需要釋放出來,當然你也可以直接 把 LWIP_TIMEVAL_PRIVATE 的值改為 1,但是為啥我當時沒那么做我也不知道了

添加源文件

准備工作完成,現在把【零】基於 open62541 項目編譯支持 STM32 平台的單獨的 open62541 源文件和頭文件中生成的 open62541.copen62541.h添加到項目工程中

添加源文件和頭文件應該不需要我多說吧

image-20200606222909853

image-20200606223612551

修改 FreeRTOS 源碼

因為之前編譯的時候勾選了 UA_ARCH_FREERTOS_USE_OWN_MEMORY,就是使用 FreeRTOS 自己的內存管理函數

freertos

但是 FreeRTOS 提供的內存管理函數中 只有pvPortMallocvPortFree 這兩個函數,沒有 pvPortCallocpvPortRealloc 函數,所以我們需要自己實現這兩個函數那是不可能的,國外已經有網友實現了,然后我拿來用了沒發現問題,所以我直接貼在下面

void *pvPortCalloc( size_t nmemb, size_t size )
{
	void *pvReturn;
	vTaskSuspendAll();
	{
		pvReturn = pvPortMalloc( nmemb*size );
		if(pvReturn)
			memset(pvReturn,0,nmemb*size);
	}
	xTaskResumeAll();

	return pvReturn;
}


void *pvPortRealloc( void *pv, size_t xWantedSize )
{
	uint8_t *puc = ( uint8_t * ) pv;
 	BlockLink_t *pxLink;
	int datasize;
 	void *pvReturn = NULL;


    if (xWantedSize == 0) {
        vPortFree(pv);
        return NULL;
    }
  
 	if (pv == NULL)
 		return pvPortMalloc(xWantedSize);
  
 	/* The memory being freed will have an BlockLink_t structure immediately
 	before it. */
 	puc -= xHeapStructSize;
	pxLink = ( BlockLink_t * ) puc;
	datasize = (pxLink->xBlockSize & ~xBlockAllocatedBit) - xHeapStructSize;
 	if (datasize >= xWantedSize) // have enough memory don't need realloc
 		return pv;

    pvReturn = pvPortMalloc(xWantedSize);
    if (pvReturn == NULL) // malloc fail, return NULL, don't free pv.
        return NULL;
  
    memcpy(pvReturn, pv, xWantedSize);
    vPortFree(pv);// realloc success, copy and free pv.
  
    return pvReturn;
}

然后在 portable.h 中添加如下代碼

void *pvPortCalloc( size_t nmemb, size_t size );
void *pvPortRealloc( void *pv, size_t xWantedSize );

修改 open62541

  • open62541.h 中重定義 int 為 ssize_t。
    image-20200901165649802
  • 聲明 OPEN62541_FEERTOS_USE_OWN_MEM,啟用 FreeRTOS 自己的內存管理函數。image-20200901165657461
  • open62541.c 中,將涉及到 AF_INET6 的內容采用宏定義給屏蔽
    image-20200901165608567
  • sockets.h 文件沒有以下內容,則添加
    #if !defined(sa_family_t) && !defined(SA_FAMILY_T_DEFINED)
    typedef u8_t sa_family_t;
    #endif
    
    
    struct sockaddr_storage {
      u8_t        s2_len;
      sa_family_t ss_family;
      char        s2_data1[2];
      u32_t       s2_data2[3];
    #if LWIP_IPV6
      u32_t       s2_data3[3];
    #endif /* LWIP_IPV6 */
    };
    

OPCUA 服務器

編寫以下代碼建立服務器

void opcua_task(void *pvParameter)
{
	//The default 64KB of memory for sending and receicing buffer caused problems to many users. With the code below, they are reduced to ~16KB
	UA_UInt32 sendBufferSize = 16000;       //64 KB was too much for my platform
	UA_UInt32 recvBufferSize = 16000;       //64 KB was too much for my platform
	UA_UInt16 portNumber = 4840;

	UA_Server* mUaServer = UA_Server_new();
	UA_ServerConfig *uaServerConfig = UA_Server_getConfig(mUaServer);
	UA_ServerConfig_setMinimalCustomBuffer(uaServerConfig, portNumber, 0, sendBufferSize, recvBufferSize);

	//VERY IMPORTANT: Set the hostname with your IP before starting the server
	UA_ServerConfig_setCustomHostname(uaServerConfig, UA_STRING("192.168.0.25"));

	//The rest is the same as the example

	UA_Boolean running = true;

//	// add a variable node to the adresspace
//	UA_VariableAttributes attr = UA_VariableAttributes_default;
//	UA_Int32 myInteger = 42;
//	UA_Variant_setScalarCopy(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
//	attr.description = UA_LOCALIZEDTEXT_ALLOC("en-US","the answer");
//	attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US","the answer");
//	UA_NodeId myIntegerNodeId = UA_NODEID_STRING_ALLOC(1, "the.answer");
//	UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME_ALLOC(1, "the answer");
//	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
//	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
//	UA_Server_addVariableNode(mUaServer, myIntegerNodeId, parentNodeId,
//																													parentReferenceNodeId, myIntegerName,
//																													UA_NODEID_NULL, attr, NULL, NULL);

//	/* allocations on the heap need to be freed */
//	UA_VariableAttributes_clear(&attr);
//	UA_NodeId_clear(&myIntegerNodeId);
//	UA_QualifiedName_clear(&myIntegerName);

	UA_StatusCode retval = UA_Server_run(mUaServer, &running);
	UA_Server_delete(mUaServer);
}

main.c完整代碼

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lwip_comm.h"
#include "LAN8720.h"
#include "usmart.h"
#include "lcd.h"
#include "sram.h"
#include "lwip/netif.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h"
#include "lwipopts.h"
#include "rtc.h"
#include "app_opcua.h"



//ÈÎÎñÓÅÏȼ¶
#define DISPLAYT_TASK_PRIO		8
#define LED_TASK_PRIO			9
#define START_TASK_PRIO			10
#define OPCUA_TASK_PRIO			11

//ÈÎÎñ¶ÑÕ»´óС
#define DISPLAY_STK_SIZE 		128  
#define LED_STK_SIZE 			128  
#define START_STK_SIZE 			256
#define OPCUA_STK_SIZE 			4096  

//ÈÎÎñ¾ä±ú
TaskHandle_t DISPLAY_Task_Handler;
TaskHandle_t LED_Task_Handler;
TaskHandle_t START_Task_Handler;
TaskHandle_t OPCUA_Task_Handler;

//ÈÎÎñº¯Êý
void display_task(void *pvParameters);
void led_task(void *pvParameters);
void start_task(void *pvParameters);
void network_task(void *pvParameters);
void opcua_task(void *pvParameters);



#define LCD_WIDTH		480
#define	LCD_LENGTH		800

#define RTC_TIME_WIDTH	170
#define LCD_LENGTH_STEP	20

u16 px,py;
u8 tbuf[40];
u8 t=0;


void RTC_time(u16 *x,u16 *y)
{
	RTC_TimeTypeDef RTC_TimeStruct;
	RTC_DateTypeDef RTC_DateStruct;
	
	RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct);
	sprintf((char*)tbuf,"20%02d-%02d-%02d ",RTC_DateStruct.RTC_Year,RTC_DateStruct.RTC_Month,RTC_DateStruct.RTC_Date); 
		
	RTC_GetTime(RTC_Format_BIN,&RTC_TimeStruct);
	sprintf((char*)tbuf + strlen((char *)tbuf),"%02d:%02d:%02d",RTC_TimeStruct.RTC_Hours,RTC_TimeStruct.RTC_Minutes,RTC_TimeStruct.RTC_Seconds); 

	LCD_ShowString(*x,*y,RTC_TIME_WIDTH,16,16,tbuf);
}

void LCD_RTC_Printf(u16 *x,u16 *y,u8 *pbuf)
{
	POINT_COLOR = BLUE; 
	RTC_time(x,y);
	POINT_COLOR = RED; 
	LCD_ShowString(*x+RTC_TIME_WIDTH,*y,LCD_WIDTH,16,16,pbuf);
	*y +=LCD_LENGTH_STEP;
}


//ÔÚLCDÉÏÏÔʾµØÖ·ÐÅÏ¢
//mode:1 ÏÔʾDHCP»ñÈ¡µ½µÄµØÖ·
//	  ÆäËû ÏÔʾ¾²Ì¬µØÖ·
void show_address(u8 mode,u16 *x,u16 *y)
{
	u8 buf[30];
	if(mode==2)
	{
		sprintf((char*)buf,"MAC    :%d.%d.%d.%d.%d.%d",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);//´òÓ¡MACµØÖ·
		LCD_RTC_Printf(&px,&py,buf); 
		sprintf((char*)buf,"DHCP IP:%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);						//´òÓ¡¶¯Ì¬IPµØÖ·
		LCD_RTC_Printf(&px,&py,buf); 
		sprintf((char*)buf,"DHCP GW:%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);	//´òÓ¡Íø¹ØµØÖ·
		LCD_RTC_Printf(&px,&py,buf);		
		sprintf((char*)buf,"DHCP IP:%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);	//´òÓ¡×ÓÍøÑÚÂëµØÖ·
		LCD_RTC_Printf(&px,&py,buf); 
	}
	else 
	{
		sprintf((char*)buf,"MAC      :%d.%d.%d.%d.%d.%d",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);//´òÓ¡MACµØÖ·
		LCD_RTC_Printf(&px,&py,buf);
		sprintf((char*)buf,"Static IP:%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);						//´òÓ¡¶¯Ì¬IPµØÖ·
		LCD_RTC_Printf(&px,&py,buf);
		sprintf((char*)buf,"Static GW:%d.%d.%d.%d",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);	//´òÓ¡Íø¹ØµØÖ·
		LCD_RTC_Printf(&px,&py,buf);
		sprintf((char*)buf,"Static IP:%d.%d.%d.%d",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);	//´òÓ¡×ÓÍøÑÚÂëµØÖ·
		LCD_RTC_Printf(&px,&py,buf);
	}	
}



/****************ÖжÏÓÅÏȼ¶*****************************************
 * TIM4			3
 * TIM3			4
 * UART			5
 * RTC	ÄÖÖÓA	8
 * RTC	»½ÐÑ	9
 * ÒÔÌ«Íø		2
 *******************************************************************/


int main(void)
{
	px=10;py=0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);		//ÉèÖÃNVICÖжϷÖ×é4
	uart_init(115200);    								//´®¿Ú²¨ÌØÂÊÉèÖÃ
	delay_init(168);       								//ÑÓʱ³õʼ»¯
	LCD_Init(); 										//LCD³õʼ»¯
	My_RTC_Init();		 								//³õʼ»¯RTC
	FSMC_SRAM_Init();									//³õʼ»¯ÍⲿSRAM  
	usmart_dev.init(84); 								//³õʼ»¯USMART
	LED_Init();  										//LED³õʼ»¯
			
#if 0
	my_mem_init(SRAMIN);								//³õʼ»¯ÄÚ²¿ÄÚ´æ³Ø
	my_mem_init(SRAMEX);								//³õʼ»¯ÍⲿÄÚ´æ³Ø
	my_mem_init(SRAMCCM);	  							//³õʼ»¯CCMÄÚ´æ³Ø

#endif
	
	LCD_RTC_Printf(&px,&py,(u8 *)"Peripheral initialization completed.");
	LCD_RTC_Printf(&px,&py,(u8 *)"init lwip ...");
	while(lwip_comm_init()) //lwip³õʼ»¯
	{
		LCD_RTC_Printf(&px,&py,(u8 *)"LWIP Init Falied!");   //lwip³õʼ»¯Ê§°Ü
		delay_ms(1200);
		LCD_RTC_Printf(&px,&py,(u8 *)"Retrying...");  
	}
	LCD_RTC_Printf(&px,&py,(u8 *)"LWIP Init Success!");    //lwip³õʼ»¯³É¹¦
	
	//´´½¨¿ªÊ¼ÈÎÎñ
	LCD_RTC_Printf(&px,&py,(u8 *)"Create start task ...");  
	xTaskCreate(start_task,"start_task",START_STK_SIZE,NULL,START_TASK_PRIO,&START_Task_Handler);             
	vTaskStartScheduler();          					//¿ªÆôÈÎÎñµ÷¶È
}

//startÈÎÎñ
void start_task(void *pvParameters)
{
	LCD_RTC_Printf(&px,&py,(u8 *)"Start task running ...");  
	taskENTER_CRITICAL();      							//½øÈëÁÙ½çÇø
	
#if LWIP_DHCP
	lwip_comm_dhcp_creat(); 							//´´½¨DHCPÈÎÎñ
#endif
	//´´½¨LEDÈÎÎñ.
	LCD_RTC_Printf(&px,&py,(u8 *)"Create led task ...");  
	xTaskCreate(led_task, "led_task",LED_STK_SIZE,NULL,LED_TASK_PRIO, &LED_Task_Handler);  
	
 	//´´½¨DISPLAYÈÎÎñ
	LCD_RTC_Printf(&px,&py,(u8 *)"Create display task ...");  
	xTaskCreate(display_task,"display_task",DISPLAY_STK_SIZE,NULL,DISPLAYT_TASK_PRIO,&DISPLAY_Task_Handler);
			  
	vTaskDelete(START_Task_Handler);						

  taskEXIT_CRITICAL();             							//Í˳öÁÙ½çÇø								
}




//ÏÔʾµØÖ·µÈÐÅÏ¢
void display_task(void *pvParameters)
{
	LCD_RTC_Printf(&px,&py,(u8 *)"Display task running");
	while(1)
	{ 

#if LWIP_DHCP									       			//µ±¿ªÆôDHCPµÄʱºò
		if(lwipdev.dhcpstatus != 0) 							//¿ªÆôDHCP
		{
			show_address(lwipdev.dhcpstatus,&px,&py );			//ÏÔʾµØÖ·ÐÅÏ¢
			vTaskDelay(500); 
			LCD_RTC_Printf(&px,&py,(u8 *)"Create opc ua task ...");  
			xTaskCreate(opcua_task,"opcua_task",OPCUA_STK_SIZE,NULL,OPCUA_TASK_PRIO,&OPCUA_Task_Handler);
			LCD_RTC_Printf(&px,&py,(u8 *)"Display task hangs");
			vTaskSuspend(DISPLAY_Task_Handler); 				//ÏÔʾÍêµØÖ·ÐÅÏ¢ºó¹ÒÆð×ÔÉíÈÎÎñ	
		}
#else
		show_address(0,&px,&py); 						        //ÏÔʾ¾²Ì¬µØÖ·
		vTaskDelay(500);      		

		LCD_RTC_Printf(&px,&py,(u8 *)"Create opc ua task ...");  
		xTaskCreate(opcua_task,"opcua_task",OPCUA_STK_SIZE,NULL,OPCUA_TASK_PRIO,&OPCUA_Task_Handler);
		
		vTaskDelete(DISPLAY_Task_Handler); 					
#endif 

		vTaskDelay(500);      							//ÑÓʱ500ms
	}
}

//ledÈÎÎñ
void led_task(void *pvParameters)
{
	LCD_RTC_Printf(&px,&py,(u8 *)"Led task running ...");
 	while(1)
	{
		LCD_Fill(0,780,RTC_TIME_WIDTH,800,WHITE);
		POINT_COLOR = BLUE; 
		RTC_TimeTypeDef RTC_TimeStruct;
		RTC_DateTypeDef RTC_DateStruct;
		
		RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct);
		sprintf((char*)tbuf,"20%02d-%02d-%02d ",RTC_DateStruct.RTC_Year,RTC_DateStruct.RTC_Month,RTC_DateStruct.RTC_Date); 
			
		RTC_GetTime(RTC_Format_BIN,&RTC_TimeStruct);
		sprintf((char*)tbuf + strlen((char *)tbuf),"%02d:%02d:%02d",RTC_TimeStruct.RTC_Hours,RTC_TimeStruct.RTC_Minutes,RTC_TimeStruct.RTC_Seconds); 

		LCD_ShowString(0,780,RTC_TIME_WIDTH,16,16,tbuf);
		
		POINT_COLOR = RED; 
		LCD_ShowString(RTC_TIME_WIDTH,780,LCD_WIDTH,16,16,(u8 *)"Total mem:");
		LCD_ShowxNum(RTC_TIME_WIDTH+80,780,configTOTAL_HEAP_SIZE/1024,4,16,0);
		LCD_ShowString(RTC_TIME_WIDTH+120,780,LCD_WIDTH,16,16,(u8 *)"KB");
		
		LCD_ShowString(RTC_TIME_WIDTH+150,780,LCD_WIDTH,16,16,(u8 *)"Remain mem:");
		
		LCD_Fill(RTC_TIME_WIDTH+240,780,480,800,WHITE);
		LCD_ShowxNum(RTC_TIME_WIDTH+240,780,xPortGetFreeHeapSize()/1024,4,16,0);
		LCD_ShowString(RTC_TIME_WIDTH+280,780,LCD_WIDTH,16,16,(u8 *)"KB");

		LED0 = !LED0;
		vTaskDelay(1000);     							 //ÑÓʱ500ms
 	}
}






void opcua_task(void *pvParameter)
{
	LCD_RTC_Printf(&px,&py,(u8 *)"Opc ua task running ...");
	
    LCD_RTC_Printf(&px,&py,(u8 *)"Set the connection config");
	
	UA_ServerConfig *config = UA_ServerConfig_new_customBuffer(4840,NULL,8192,8192);
	UA_ServerConfig_set_customHostname(config,UA_String_fromChars("192.168.31.231"));
	

    UA_Server *server = UA_Server_new(config);

	/* Add a variable node */
    /* 1) Define the node attributes */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer");
    UA_Int32 myInteger = 42;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);

    /* 2) Define where the node shall be added with which browsename */
    UA_NodeId newNodeId = UA_NODEID_STRING(1, "the.answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableType = UA_NODEID_NULL; /* take the default variable type */
    UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, "the answer");

    /* 3) Add the node */
    UA_Server_addVariableNode(server, newNodeId, parentNodeId, parentReferenceNodeId,
                              browseName, variableType, attr, NULL, NULL);

	LCD_RTC_Printf(&px,&py,(u8 *)"OPC UA server running.");
	UA_Server_run(server, &running);

    UA_Server_delete(server);
    UA_ServerConfig_delete(config);

    vTaskDelete(NULL);
}

編譯

如果以上步驟都沒問題,那此時點擊該按鈕image-20210722010958241之后就會無錯誤的編譯成功

image-20210722092819246

連接

采用 UaExport 連接 OPCUA 服務器,連接成功后如圖所示

image-20200903095841819

至此,open62541 移植到 STM32 就已經大功告成@(太開心)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM