POWERLINK协议在stm32单片机+w5500移植成功经验分享

news2025/1/6 20:11:12

连续折腾了多个晚上,又趁周末又花了一天时间,终于把powerlink协议移植成功到单片机上啦。本想放弃,但想了下不管我能不能用上,结个尾吧,分享给有需要的人。放弃并不难,但坚持一定很酷。为了移植测试这个协议花了不少血本。stm32开发板就买了两套,其中第一套板子在移植过程中发现内存不够用,型号买小啦,最后买了stm32F407ZGT6的开发板。

前言

STM32F407ZGT6芯片资源1M的falsh,192k的内存够用了。买的第一套Alientek的miniSTM32开发板芯片型号stm32F103RCT6,芯片资源256k的flash,48k的ram不够用(主要是ram不够用)。因为移植过程中发现这powerlink协议栈挺占内存的,rom倒是占的不大。

汇总下资源占用情况,分享给有需要的人参考。(资源包含嵌入式系统RTX内核源码和从站demo功能源码在内)

Program Size: Code=94008 RO-data=15352 RW-data=4204 ZI-data=62212  

RW-data+ZI-data 内存占用 4204+62212  = 66416,超过60K了,主要是字典文件占用内存大。Rom占用:94k。

我使用的开发板如下图所示长这样:

 移植工程结构:

从工程结构上看,我把涉及改动的文件都单独放到port文件夹了。里面涉及的文件挺多的,不过好在代码量不太大。使用Keil自带的RTX嵌入式内核系统的相关特性,移植不算难。

移植过程

协议栈移植

移植过程参考之前分享的一篇文章《POWERLINK协议源码(最新)在stm32单片机上的移植指南》POWERLINK协议源码(最新)在stm32单片机上的移植指南,把涉及的相关文件摘出来。

屏蔽掉跟系统或驱动相关的接口和编译错误,建立工程目录结构。

netif-stm32.c和target-stm32.c代码量不大,很好移植。

target-stm32.c中主要涉及target_msleep,target_enableGlobalInterrupt,target_getTickCount等的实现。使用RTX系统的相关api实现即可。target_setIpAdrs接口不需要,留空即可。

target-mutex.c文件移植:

/**
\brief  Create Mutex

The function creates a mutex.

\param[in]      mutexName_p         The name of the mutex to create.
\param[out]     pMutex_p            Pointer to store the created mutex.

\return The function returns a tOplkError error code.
\retval kErrorOk                    Mutex was successfully created.
\retval kErrorNoFreeInstance        An error occurred while creating the mutex.

\ingroup module_target
*/
//------------------------------------------------------------------------------
tOplkError target_createMutex(const char* mutexName_p,
                              OPLK_MUTEX_T* pMutex_p)
{

  UNUSED_PARAMETER(mutexName_p);
	pMutex_p =  osMutexNew(NULL);
	return kErrorOk;
}

//------------------------------------------------------------------------------
/**
\brief  Destroy Mutex

The function destroys a mutex.

\param[in]      mutexId_p           The ID of the mutex to destroy.

\ingroup module_target
*/
//------------------------------------------------------------------------------
void target_destroyMutex(OPLK_MUTEX_T mutexId_p)
{
//CloseHandle(mutexId_p);
	if(mutexId_p != NULL){
		osMutexDelete(mutexId_p);
	}
}

//------------------------------------------------------------------------------
/**
\brief  Lock Mutex

The function locks a mutex.

\param[in]      mutexId_p           The ID of the mutex to lock.

\return The function returns a tOplkError error code.
\retval kErrorOk                    Mutex was successfully locked.
\retval kErrorNoFreeInstance        An error occurred while locking the mutex.

\ingroup module_target
*/
//------------------------------------------------------------------------------
tOplkError target_lockMutex(OPLK_MUTEX_T mutexId_p)
{
  
    tOplkError  ret;
	  osStatus_t status;
 
    ret = kErrorOk;
		if (mutexId_p != NULL) {
			status = osMutexAcquire(mutexId_p, osWaitForever);
			if (status != osOK)  {
				// handle failure code
			}
		}    
    return ret;
}

//------------------------------------------------------------------------------
/**
\brief  Unlock Mutex

The function unlocks a mutex.

\param[in]      mutexId_p           The ID of the mutex to unlock.

\ingroup module_target
*/
//------------------------------------------------------------------------------
void target_unlockMutex(OPLK_MUTEX_T mutexId_p)
{
    //ReleaseMutex(mutexId_p);
	osStatus_t status;
 
  if (mutexId_p != NULL)  {
    status = osMutexRelease(mutexId_p);
    if (status != osOK)  {
      // handle failure code
    }
  }
}

int target_lock(void)
{
    target_enableGlobalInterrupt(FALSE);

    return 0;
}
int target_unlock(void)
{
    target_enableGlobalInterrupt(TRUE);

    return 0;
}

使用RTX的Mutex互斥量api接口,这部分很容易移植。

circbuf-stm32.c文件中,主要涉及加锁和解锁,也好移植。

//------------------------------------------------------------------------------
/**
\brief  Lock circular buffer

The function enters a locked section of the circular buffer.

\param[in]      pInstance_p         Pointer to circular buffer instance.

\ingroup module_lib_circbuf
*/
//------------------------------------------------------------------------------
void circbuf_lock(tCircBufInstance* pInstance_p)
{
    osStatus_t              waitResult;
    tCircBufArchInstance*   pArchInstance;

    // Check parameter validity
    ASSERT(pInstance_p != NULL);

    pArchInstance = (tCircBufArchInstance*)pInstance_p->pCircBufArchInstance;
	  waitResult = osMutexAcquire(pArchInstance->lockMutex, osWaitForever);
		switch (waitResult) {
				case osOK:
					break;
				default:
					DEBUG_LVL_ERROR_TRACE("%s() Mutex wait unknown error! Error:%ld\n",
                                  __func__);
					break;
			}
}

//------------------------------------------------------------------------------
/**
\brief  Unlock circular buffer

The function leaves a locked section of the circular buffer.

\param[in]      pInstance_p         Pointer to circular buffer instance.

\ingroup module_lib_circbuf
*/
//------------------------------------------------------------------------------
void circbuf_unlock(tCircBufInstance* pInstance_p)
{
    tCircBufArchInstance* pArchInstance;

    // Check parameter validity
    ASSERT(pInstance_p != NULL);

    pArchInstance = (tCircBufArchInstance*)pInstance_p->pCircBufArchInstance;
    osMutexRelease(pArchInstance->lockMutex);
}

 eventkcal-stm32.c文件移植:

 这个参考了eventkcal-win32.c的实现,比 eventkcal-linux.c的简单些。使用RTX的信号量机制,实现替代也不难。

//------------------------------------------------------------------------------
/**
\brief  Ethernet driver initialization

This function initializes the Ethernet driver.

\param[in]      pEdrvInitParam_p    Edrv initialization parameters

\return The function returns a tOplkError error code.

\ingroup module_edrv
*/
//------------------------------------------------------------------------------
tOplkError edrv_init(const tEdrvInitParam* pEdrvInitParam_p)
{

    // Check parameter validity
    ASSERT(pEdrvInitParam_p != NULL);

    // Clear instance structure
    OPLK_MEMSET(&edrvInstance_l, 0, sizeof(edrvInstance_l));

    if (pEdrvInitParam_p->pDevName == NULL)
        return kErrorEdrvInit;

    // Save the init data
    edrvInstance_l.initParam = *pEdrvInitParam_p;

    edrvInstance_l.fStartCommunication = TRUE;
    edrvInstance_l.fThreadIsExited = FALSE;

    // If no MAC address was specified read MAC address of used
    // Ethernet interface
    if ((edrvInstance_l.initParam.aMacAddr[0] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[1] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[2] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[3] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[4] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[5] == 0))
    {   // read MAC address from controller
        getMacAdrs(edrvInstance_l.initParam.pDevName,
                   edrvInstance_l.initParam.aMacAddr);
    }

	edrvInstance_l.sock = socket(0, Sn_MR_MACRAW, 0,0);
    if (edrvInstance_l.sock < 0)
    {
        DEBUG_LVL_ERROR_TRACE("%s() cannot open socket\n", __func__);
        return kErrorEdrvInit;
    }

    edrvInstance_l.hThread = osThreadNew(workerThread,&edrvInstance_l,NULL);
//    // wait until thread is started
//    sem_wait(&edrvInstance_l.syncSem);

    return kErrorOk;
}
//------------------------------------------------------------------------------
/**
\brief  Event handler thread function

This function contains the main function for the event handler thread.

\param[in]      arg                 Thread parameter. Used to get the instance structure.

\return The function returns the thread exit code.
*/
//------------------------------------------------------------------------------
static void eventThread(void* arg)
{
    const tEventkCalInstance*   pInstance = (const tEventkCalInstance*)arg;
    osStatus_t waitResult;

    DEBUG_LVL_EVENTK_TRACE("Kernel event thread %d waiting for events...\n", GetCurrentThreadId());
    while (!pInstance->fStopThread)
    {
      waitResult = osSemaphoreAcquire(pInstance->semKernelData, 100UL);       // wait for max. 10 ticks for semaphore token to get available
			switch (waitResult) {
				case osOK:
					if (eventkcal_getEventCountCircbuf(kEventQueueKInt) > 0)
					{
							eventkcal_processEventCircbuf(kEventQueueKInt);
					}
					else
					{
							if (eventkcal_getEventCountCircbuf(kEventQueueU2K) > 0)
							{
									eventkcal_processEventCircbuf(kEventQueueU2K);
							}
					}
					break;
				case osErrorResource:
					DEBUG_LVL_ERROR_TRACE("kernel event osErrorResource!\n");
					break;
				case osErrorParameter:
					DEBUG_LVL_ERROR_TRACE("kernel event osErrorParameter!\n");
					break;
				case osErrorTimeout:
					DEBUG_LVL_ERROR_TRACE("kernel event timeout!\n");
					break;
				default:
					DEBUG_LVL_ERROR_TRACE("%s() Semaphore wait unknown error! \n",
                                      __func__);
					break;
			}
    }

    DEBUG_LVL_EVENTK_TRACE("Kernel event thread is exiting!\n");

}

edrv-rawsock_stm32.c文件移植:

这个很重要,网格底层通信相关的都在这个文件里。使用w5500模块提供的api,操作原始MAC报文帧的方式实现。pthread_mutex_lock和sem_post这些linux系统的互斥量和信号量等,都用RTX提供的相关接口替换。

//------------------------------------------------------------------------------
/**
\brief  Send Tx buffer

This function sends the Tx buffer.

\param[in,out]  pBuffer_p           Tx buffer descriptor

\return The function returns a tOplkError error code.

\ingroup module_edrv
*/
//------------------------------------------------------------------------------
tOplkError edrv_sendTxBuffer(tEdrvTxBuffer* pBuffer_p)
{
    int    sockRet;

    // Check parameter validity
    ASSERT(pBuffer_p != NULL);

    FTRACE_MARKER("%s", __func__);

    if (pBuffer_p->txBufferNumber.pArg != NULL)
        return kErrorInvalidOperation;

    if (getLinkStatus(edrvInstance_l.initParam.pDevName) == FALSE)
    {
        /* If there is no link, we pretend that the packet is sent and immediately call
         * tx handler. Otherwise the stack would hang! */
        if (pBuffer_p->pfnTxHandler != NULL)
        {
            pBuffer_p->pfnTxHandler(pBuffer_p);
        }
    }
    else
    {
        //pthread_mutex_lock(&edrvInstance_l.mutex);
			  osMutexAcquire(edrvInstance_l.mutex,osWaitForever);
        if (edrvInstance_l.pTransmittedTxBufferLastEntry == NULL)
        {
            edrvInstance_l.pTransmittedTxBufferLastEntry = pBuffer_p;
            edrvInstance_l.pTransmittedTxBufferFirstEntry = pBuffer_p;
        }
        else
        {
            edrvInstance_l.pTransmittedTxBufferLastEntry->txBufferNumber.pArg = pBuffer_p;
            edrvInstance_l.pTransmittedTxBufferLastEntry = pBuffer_p;
        }
        //pthread_mutex_unlock(&edrvInstance_l.mutex);
				osMutexRelease(edrvInstance_l.mutex);

        sockRet = send(edrvInstance_l.sock, (u_char*)pBuffer_p->pBuffer, (int)pBuffer_p->txFrameSize);
        if (sockRet < 0)
        {
            DEBUG_LVL_EDRV_TRACE("%s() send() returned %d\n", __func__, sockRet);
            return kErrorInvalidOperation;
        }
        else
        {
            packetHandler((u_char*)&edrvInstance_l, sockRet, pBuffer_p->pBuffer);
        }
    }

    return kErrorOk;
}
//------------------------------------------------------------------------------
/**
\brief  Edrv worker thread

This function implements the edrv worker thread. It is responsible to receive frames

\param[in,out]  pArgument_p         User specific pointer pointing to the instance structure

\return The function returns a thread error code.
*/
//------------------------------------------------------------------------------
static void workerThread(void* pArgument_p)
{
    tEdrvInstance*  pInstance = (tEdrvInstance*)pArgument_p;
    int             rawSockRet;
    u_char          aBuffer[EDRV_MAX_FRAME_SIZE];

    DEBUG_LVL_EDRV_TRACE("%s(): ThreadId:%ld\n", __func__, syscall(SYS_gettid));

    // signal that thread is successfully started
    //sem_post(&pInstance->syncSem);
	 osSemaphoreRelease(pInstance->syncSem);

    while (edrvInstance_l.fStartCommunication)
    {
        rawSockRet = recvfrom(edrvInstance_l.sock, aBuffer, EDRV_MAX_FRAME_SIZE, 0, 0);
        if (rawSockRet > 0)
        {
            packetHandler(pInstance, rawSockRet, aBuffer);
        }
    }
    edrvInstance_l.fThreadIsExited = TRUE;

}

从站demo移植

移植从站的demo, demo_cn_console文件夹里的从站demo,在上述协议栈成功移植的基础上,这部分从站demo移植很简单。

/*
** main function
**
**  Arguments:
**      none
**   
*/ 
int main (int argc, char* argv[]) 
{
  tOplkError  ret = kErrorOk;
	tOptions    opts;

	// System Initialization
  SystemCoreClockUpdate();
	
  if (getOptions(argc, argv, &opts) < 0)
     return 0;
	
  LED_Initialize();
	uart_init();
	//stdout_init();
	printf("hello test\r\n");
	LED_On(2);
	spi_init();
	
	reset_w5500();
	set_w5500_mac();
	set_w5500_ip();
	
	eventlog_init(opts.logFormat,
                  opts.logLevel,
                  opts.logCategory,
                  (tEventlogOutputCb)console_printlogadd);

	initEvents(&fGsOff_l);

	printf("----------------------------------------------------\n");
	printf("openPOWERLINK console CN DEMO application\n");
	printf("Using openPOWERLINK stack: %s\n", oplk_getVersionString());
	printf("----------------------------------------------------\n");

	eventlog_printMessage(kEventlogLevelInfo,
												kEventlogCategoryGeneric,
												"demo_cn_console: Stack version:%s Stack configuration:0x%08X",
												oplk_getVersionString(),
												oplk_getStackConfiguration());

	ret = initPowerlink(CYCLE_LEN,
											opts.devName,
											aMacAddr_l,
											opts.nodeId);
	if (ret != kErrorOk)
			goto Exit;

	ret = initApp();
	if (ret != kErrorOk)
			goto Exit;
 
  osKernelInitialize();                       // Initialize CMSIS-RTOS
  osThreadNew(Main_Loop_Thread, NULL, NULL);   // Create application main thread
  osThreadNew(LED_Blink_PortE, NULL, NULL);   // Create application test thread
  osKernelStart();                            // Start thread execution
  for (;;) 
  {
    //Dummy infinite for loop.
  }
Exit:	
	 printf("openPOWERLINK console Exit\n");
	 shutdownApp();
   shutdownPowerlink();
	 return 0;
}

如何使用

完成上述移植过程后,需要下载到板子上运行。需要配置好串口管脚,方便串口输出日志调试看。spi的管脚也需要根据板子上的实际资源配置好。然后接上网线,先运行起来主站,然后运行从站,结合串口打印日志调试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/581240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Arcgis小技巧【13】——和数据统计相关的各种工具

在Arcgis中可以通过属性表中字段的【统计】功能或使用统计相关的工具对属性表进行数据统计。 在Arcgis工具箱中有一组【统计分析】工具集&#xff0c;不仅包含对属性数据执行标准统计分析&#xff08;例如平均值、最小值、最大值和标准差&#xff09;的工具&#xff0c;也包含…

表的增删改查

目录 表的增删改查create(创建)单行数据 全列插入多行数据 指定列插入插入否则更新替换 retrieve(读取)SELECT 列全列查询指定列查询查询字段为表达式为查询结果指定别名结果去重 WHERE 条件英语不及格的同学及英语成绩 ( < 60 )&#xff08;<&#xff09;语文成绩在 […

个人博客-SpringBoot+Vue3项目实战(6)- 二次封装Axios

目录 前言新建axiosUtil.js 文件基本配置统一URL.env文件与环境变量示例参考资料 请求头超时时间 request 拦截器response 拦截器统一Api管理测试 前言 在上文中&#xff0c;我们封装了统一的后端数据返回结果&#xff0c;有了标准化的接口数据&#xff0c;我们就可以针对它&a…

【Linux性能优化】你知道什么是平衡负载么

什么是平衡负载 首先大家思考一下&#xff0c;当你发现自己的服务变慢时&#xff0c;你会首先使用什么命令来排查&#xff1f;我通常做的第一件事&#xff0c;就是执行top或者uptime命令来了解系统的负载情况。比如像下面这样&#xff0c;我在命令行里输入top命令&#xff0c;…

Springboot +spring security,创建SpringSecurity项目

一.简介 搭建第一个Spring Security项目&#xff0c;看看如何利用Spring Security来保护Java Web项目。 二. 创建SpringSecurity项目 我们这边使用idea进行创建。 2.1创建一个基于Maven的Project项目。 2.2设置项目名称和存储位置 2.3添加项目依赖 在pom.xml文件中&#x…

LeetCode 128 最长连续序列

LeetCode 128 最长连续序列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/longest-consecutive-sequence/description/ 博主Github&#xff1a;https://github.com/GDUT-Rp/LeetCode 题目&#xff1a; 给定一个未排…

【双系统ubuntu安装指引】配置一个顺手的深度学习环境

文章目录 前言1. 前置安装 前言 版本&#xff1a;ubuntu20.04 桌面版 normal安装 第一件事&#xff0c;切换源&#xff1a;换成阿里源 https://blog.csdn.net/u010092716/article/details/125832062 第二件事输入法安装&#xff0c;指引在这里 https://blog.csdn.net/q54434431…

JMeter性能测试:JMeter多用户并发模拟及压测结果分析

目录 JMeter设置 JMeter线程组 JMeter压测实例 View Results Tree Aggregate Report 命令行方式执行压测 jtl文件解析 JMeter多用户并发模拟 JMeter设置 多用户并发数的多少与计算机内存有关&#xff0c;设置 jmeter.bat (Windows) 或者 jmeter.sh (Linux)&#xff1a;…

chapter6:SpringBoot与Docker

1. 简介 Docker是一个开源的轻量级应用容器引擎, 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到Linux机器中实现虚拟化。(沙箱机制) Docker支持将软件编译成一个镜像&#xff1b;然后在镜像中各种软件做好配置&#xff0c;将镜像…

《数据库系统概论》期末考试手写笔记汇总+考试注意事项+反思(超全整理总结!!!)

&#xff08;一&#xff09;期末考试手写笔记汇总 笔记内容为期末考试前整理&#xff08;结合测试题PPT作业题目课本&#xff09; 很多内容为纯手写&#xff0c;非常的全乎&#xff0c;预祝你期末可以考个好成绩&#x1f339; 第二章第三章&#xff08;25分&#xff09; (…

JS 深度克隆的实现方法

方法一&#xff1a;正统做法&#xff08;扩展性高&#xff0c;推荐&#xff09; function test() { this.a 1; this.b 2; } test.prototype.c 3; // 原型上的属性 const obj new test(); console.log("原对象", obj); console.log("克隆后的对象", dee…

Flume系列:案例-Flume负载均衡和故障转移

目录 Apache Hadoop生态-目录汇总-持续更新 逻辑&#xff1a; 2&#xff1a;案例需求-实现故障转移 3&#xff1a;实现步骤&#xff1a; 2.1&#xff1a;实现flume1.conf 2.2&#xff1a;实现flume2.conf - 端口4141 2.3&#xff1a;实现flume3.conf - 端口4142 3&#…

NIO编程总结

NIO&#xff08;Non-blocking I/O&#xff0c;在Java领域&#xff0c;也称为New I/O&#xff09;&#xff0c;是一种同步非阻塞的I/O模型&#xff0c;也是I/O多路复用的基础&#xff0c;已经被越来越多地应用到大型应用服务器&#xff0c;成为解决高并发与大量连接、I/O处理问题…

【如何在Java中使用ForkJoinPool】

目录 背景1.使用ForkJoinPool的线程池2.工作窃取算法3.ForkJoinPool的主要类4.使用递归操作5.资源任务6.何时使用ForkJoinPool7.总结 背景 使用ForkJoinPool去分解计算密集型任务且且并行地执行他们以获得更好的Java应用程序的性能。 ForkJoinPool是一个功能强大的Java类&…

程序员进银行科技岗——简单总结

银行的分类 Top0—中央银行&#xff1a; 仅有一家&#xff0c;即中国人民银行。 Top1—政策性银行&#xff1a; 国家开发银行、中国进出口银行、中国农业发展银行 Top2—国有商业银行&#xff1a; 国有六大行&#xff08;中国工商银行、中国农业银行、中国银行、中国建设…

【计算机网络】前后端分离,HTTP协议,网络分层结构,TCP

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 前后端分类HTTP协议HTTP组成HTTP的版本HTTP的请求方式HTTP请求头HTTP 响应状态码 AJAX发送请求 …

555定时器的基本原理和应用案例

前言 555定时器常用于脉冲波形的产生和整形电路中&#xff0c;之前在查找555定时器的原理图和基本管脚信息时&#xff0c;网上的内容大多含糊不清&#xff0c;没有讲的很详细&#xff0c;要么只是单一的管脚图&#xff0c;要么就是简单的文字解释&#xff0c;并且大多数缺乏基…

2023 年大厂实习前端面试题(一):跨域问题

1. 跨域 1.1 跨域问题来源 跨域问题的来源是浏览器为了请求安全而引入的基于同源策略&#xff08;Same-origin policy&#xff09;的安全特性。 同源策略是浏览器一个非常重要的安全策略&#xff0c;基于这个策略可以限制非同源的内容与当前页面进行交互&#xff0c;从而减少…

linux 条件变量 pthread_cond_signal

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 简介 应用场景 与互斥量/信号量的区别 接口介绍 变量定义 初始化 等待被唤…

ROS:ROS的一些基本命令行

目录 一、打开小海龟1.1终端&#xff0c;启动ROS Master&#xff1a;1.2终端2&#xff0c;启动小海龟仿真器&#xff1a;1.3终端3&#xff0c;启动海龟控制节点&#xff1a; 二、查看系统中的计算图三、节点命令3.1查看节点下的命令rosnode3.2显示节点列表rosnode list3.3查看节…