【三】基于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 就已经大功告成@(太开心)

posted @ 2022-01-11 21:38  丶吃鱼的猫  阅读(3540)  评论(25编辑  收藏  举报