以下内容大致于 2020 年 9 月左右在 stm32 平台再次编译完成,但是没有 发布出来,本文中涉及到的代码可能已经发生变化,请以官方代码仓库为准
准备 FreeRTOS+LwIP
前面编译单文件的就说,open62541 是建立在系统上的,所以在移植前,需要准备一份运行良好的 STM32 FressRTO+Lwip 的代码,没有的可以在 GitHub 上STM32F4-FreeRTOS-LwIP下载,代码具体信息在 readme.md 中有说。编译 stm32 代码的 IDE 根据个人喜好,我使用的是 keil 5。
当 FreeRTOS+LwIP 没问题之后就可以开始下一步了。
编译条件
添加宏定义
- 在 FreeRTOS+LWIP 项目中添加架构宏——UA_ARCHITECTURE_FREERTOSLWIP
- 添加 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
- 添加 FreeRTOS 宏
// 在`FreeRTOSConfig.h`文件添加如下内容 #define configCHECK_FOR_STACK_OVERFLOW 1 #define configUSE_MALLOC_FAILED_HOOK 1
注释
注释掉 sockets.h
文件的 315~319 行、324 行等
注释前:
注释后:
这个是因为 open62541 中用到了这个结构体,但是原本的 LwIP 没有用的,所以需要释放出来,当然你也可以直接 把 LWIP_TIMEVAL_PRIVATE
的值改为 1,但是为啥我当时没那么做我也不知道了
添加源文件
准备工作完成,现在把【零】基于 open62541 项目编译支持 STM32 平台的单独的 open62541 源文件和头文件中生成的 open62541.c
和 open62541.h
添加到项目工程中
添加源文件和头文件应该不需要我多说吧
修改 FreeRTOS 源码
因为之前编译的时候勾选了 UA_ARCH_FREERTOS_USE_OWN_MEMORY
,就是使用 FreeRTOS 自己的内存管理函数
但是 FreeRTOS 提供的内存管理函数中 只有pvPortMalloc
和 vPortFree
这两个函数,没有 pvPortCalloc
,pvPortRealloc
函数,所以我们需要自己实现这两个函数那是不可能的,国外已经有网友实现了,然后我拿来用了没发现问题,所以我直接贴在下面
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。
- 声明
OPEN62541_FEERTOS_USE_OWN_MEM
,启用 FreeRTOS 自己的内存管理函数。 - 在
open62541.c
中,将涉及到AF_INET6
的内容采用宏定义给屏蔽
- 若
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);
}
编译
如果以上步骤都没问题,那此时点击该按钮之后就会无错误的编译成功
连接
采用 UaExport 连接 OPCUA 服务器,连接成功后如图所示
至此,open62541 移植到 STM32 就已经大功告成@(太开心)