物联网可编程逻辑控制器(二)

本文介绍了如何将eCLR内核移植到MiCO3.0系统,特别是在MiCOKit-Nucleo开发板上运行,并在MULTIPROG中编写逻辑代码。内容涉及eCLR所需的存储器空间分析,包括静态库、堆、数据内存、代码内存和堆栈空间。此外,还讨论了移植工作中涉及的Thread、Event、Mutex接口、ImageFile、Filesystem、Clock接口以及Socket通讯接口的实现。

OK在今天章节里我们全面进入MiCO3.0时代,我们首先将eCLR运行内核移植到MiCO3.0系统里并让eCLR内核能够在MiCOKit-Nucleo开发板上运行起来并且能够在MULTIPROG中编写一段简单的逻辑代码使其下载到MiCOKit-Nucleo开发板上运行。在开始正式移植工作前,我们还是需要来了解下eCLR内核对于MCU的存储器要求。

 

前面章节我们已经介绍到了,eCLR内核是由一些源代码还有核心库文件组成,如果需要运行起eCLR内核,那么我们至少需要下图所示存储器空间:

 

2.1

上面图示中说明的eCLR占用存储器并不包含MiCO系统以及其他组件所占用的存储器空间,所以大家应该明白了在前面章节我提到的为啥128KB RAM512KB FLASH空间并不太足够(大家可以看看默认的MiCO系统编译出来的map文件),下面我们分别介绍下eCLR所需要的这些存储空间的简单分析:

1. 关于eCLR内核静态库的空间占用大概需要10KBRAM以及80KBFlash,这也是把eclrlib_M4_VFP_MT_InPlace.a加入到IAR工程中编译看到实际map文件中的结果,这部分空间占用是固定不动的,IAR在链接过程中就会需要这么大的空间。

 

2. eCLR HeapeCLR内核内部的内存管理单元所需要的一个固定的存储器空间,一般情况下,在嵌入式系统上我们会使用eCLR的内存管理机制来进行申请释放RAM空间,对别是针对C++的类的对象申请与释放,我们都会重载下运算符new, delete,这样我们就可以直接通过newdelete来从eCLR内存管理单元里分配和释放内存了,所以这里的大小我们可以根据自己的需求动态来进行调整,16KB是建议的最小值,在MiCOKit-Nucleo开发板上,我们就使用最小值16KB,例如从下面图中我们可以看到这里我们重载了newdelete运算符。

 

2.2

 

3. Data memory是我们在MULTIPROG中定义变量所存放的存储器区域。例如下面截图,我们在MULTIPROG定义了变量,当编译好的工程被下载到MiCOKit-Nucleo开发板上时,eCLR内核就会根据工程信息把这些定义在MULTIPROG里面的变量分配在Data memory上,也就是说,Data memory区域的大小决定了在MULTIPROG中到底能够定义多少变量,根据我们的经验,一般情况下我们针对简单逻辑控制的小型控制器可以给出8-12KB空间也是足够使用的,当然如果设备RAM足够大,建议还是给大点空间。

 

2.3

 

4. Code memory是用来存储MULTIPROG编写的工程。也就是说,MULTIPROG中编写的逻辑代码最终经过编译后,会经过与eCLR内核通讯下载到MiCOKit-Nucleo开发板上的存储器区域里,可以是RAM也可以是FLASH,一般情况下我们要给出足够大小的Code memory,不然是写不了多少逻辑代码的,所以推荐的最小值我们也要保证128KB的存储器空间,很明显仅仅使用Cortex-M4单芯片内部的RAM存储MULTIPROG编译后的工程是不合适的,好在eCLR可以工程直接下载到内部FLASH上并且直接在FLASH上执行MULTIPROG编写的逻辑代码,我们在MiCOKit-Nucleo开发板上也是直接将逻辑代码直接下载到FLASH上执行,这样PLC逻辑执行速度还更快一点。(啰嗦一句,如果万一将来使用的下载逻辑代码到RAM中执行,那么是需要一个外部存储器来存储这个工程镜像,不然掉电了,运行的逻辑代码就会丢失了)下图是我们MULTIPROG编译默认示例工程的数据空间(Data memory)与程序空间(Code memory)提示信息:

 

2.4

5. 最后是我们PLC任务所需要的堆栈空间了,MULTIPROG中可以定义多个任务,其实这个任务最终在MiCOKit-Nucleo开发板上也会由eCLR内核调用FreeRTOS接口来创建对应的OS任务负责执行PLC的程序,如下图所示这个示例工程中定义了两个周期任务T_100msDANCE_R

 

2.5

这里每个任务下面都插入了一个或者多个程序实例,这与我们在MiCO系统里创建一个任务并且制定一个入口函数去掉用不同的功能是非常类似的概念。看到这里,各位小伙伴是不是觉得MULTIPROG编程相对C/C++很简洁呢,因为在这里我们只是做移植eCLR内核到MiCO3.0系统的工作,还没有涉及到MULTIPROG编程讲解,大家在后面会逐步了解到MULTIPROG相对于C/C++编程的核心优势。

 

前面介绍基础概念的工作我们也做了半天了,或多或少也能够猜到eCLR内核移植到MiCO系统上需要做哪些工作:

1. Thread, Event, Mutex接口实现

Thread,Event,Mutex接口我们需要使用RTOSAPI进行实现对应的功能,这样eCLR内核才能够正常去创建PLC工程中所配置的任务以及用于与MULTIPROG联机的通讯任务。这里我们拿出一个代码片段以便于理解eCLR内核通过Thread接口创建任务的过程:

 

2.6

 

可以看到eCLR内核是通过调用CThreadImpl类来实现任务创建工作,而在这个接口里面又调用了FreeRTOS的任务接口创建PLC的任务。同样类似的实现还有:

bool CThreadImpl::SetPriority(int prioThread)

void CThreadImpl::Start()

void CThreadImpl::Resume()

void CThreadImpl::Suspend()

void CThreadImpl::Sleep(int ms)

CThreadImpl::ThreadId CThreadImpl::GetThreadId()

……

这些接口都需要使用FreeRTOS的接口来直接实现,由于MiCO封装的FreeRTOS并不能完全满足这些接口的需求,所以我们就只能直接使用原生的FreeRTOS接口了。

 

Event接口主要是用于实现eCLR内部多任务之间的同步,所以我们就使用FreeRTOSSemaphore实现了,例如通过vSemaphoreCreateBinary创建一个Semaphore,使用xSemaphoreTake来等待一个事件,使用xSemaphoreGive来产生一个事件。由于接口比较简单,主要实现的代码接口是:

CEventImpl* CEventImpl::Create(bool initialstate, bool isAutoReset);

void CEventImpl::Release();

bool CEventImpl::Set();

bool CEventImpl::WaitOne();

 

Mutex接口主要用于eCLR内部资源的互斥访问,也是对应于FreeRTOSMutex接口,使用xSemaphoreCreateMutex接口进行创建,使用xSemaphoreTake与xSemaphoreGive来进如互斥区以及离开互斥区,主要实现的接口是:

CMutexImpl* CMutexImpl::Create();

void CMutexImpl::Release();

void CMutexImpl::Enter();

void CMutexImpl::Leave();

 

2. ImageFile接口实现

ImageFile接口主要用于读写存储在STM32F411内部FLASH上的PLC工程,所以主要涉及到一些擦写Flash操作,需要特别指出的是,在针对STM32F4的实现里面,最开始我是采用STM32F4标准外设库的API进行擦写内部用于PLC工程的128KBFLASH区域,在移植过程中想想还是做一个通用的解决方案比较好,所以我这边就借用了MiCO的分区方式,在platform.h里面添加了自己定义的分区:

typedef enum

{

  MiCO_PARTITION_FILESYS,

  MiCO_PARTITION_ECLR,

  MiCO_PARTITION_USER_MAX

} MiCO_user_partition_t;

 

然后在platform.cMiCO_partitions定义里增加eCLRPartition定义:

[MiCO_PARTITION_ECLR] =

{

.partition_owner         = MiCO_FLASH_EMBEDDED,

.partition_description     = "ECLR",

.partition_start_addr      = 0x08060000,

.partition_length         =    0x20000,   //128k bytes

.partition_options        = PAR_OPT_READ_EN | PAR_OPT_WRITE_EN,

},

 

这样在实现ImageFile读写时就直接调用MiCOAPI就可以了,以后我们如果要修改eCLRPLC工程存储区域也非常方便,直接修改对应的MiCO_partitions即可。如果是更换其他Cortex-M3芯片也不在话下,因为MiCO已经把这部分API都做好了,我们就直接使用会更加方便。

 

3. Filesystem接口实现

eCLR文件系统接口支持主要是在MULTIPROG里面有文件操作的功能模块可供用户进行调用,所以在eCLR内核里需要适配对应的接口,因为MiCO已经提供了FatFS的移植,并且可以在MiCO_partitions中定义文件系统区的定义,例如这里我使用的是在外部的SPI FLASH上实现文件系统,那么就需要增加下面文件系统区的定义,1MB空间勉强可以用用,如果成本不是一个大问题的话,希望EMW3166可以有升级模块增加SPI FLASH的存储器空间,例如增加到4-8MB大小,对于物联网应用来说,本地存储其实也是很重要的,有足够大小的文件系统,可以保存更多的数据在本地进行分析而不仅仅是依赖云端:

[MiCO_PARTITION_FILESYS] =

{

  .partition_owner           = MiCO_FLASH_SPI,

  .partition_description     = "FILESYS",

  .partition_start_addr      = 0x100000,

  .partition_length          = 0x100000, //1M bytes

  .partition_options         = PAR_OPT_READ_EN | PAR_OPT_WRITE_EN,

}

 

 

4. Clock接口实现

Clock接口主要是eCLR需要知道当前的系统心跳TickCount,我们可以直接使用MiCOAPI来实现即可:

uint32 EclrEnvironment::GetTickCount() //resulotion is millisecond

{

return MiCO_rtos_get_time();

}

 

uint32 EclrEnvironment::GetMicroTickCount() // resolution is microsecond

{

return MiCO_rtos_get_time() * 1000;

}

 

5. eCLR Socket通讯接口实现

eCLR Socket通讯接口并不完全是我们说的LwIPsocket接口用于TCP/IP的实现,而是更加通用的一种通信框架,当然我们可以使用LwIPsocket来实现eCLR Socket接口,这样我们就可以通过WIFIPC上的MULTIPROGMiCOKit-Nucleo开发板连接起来了。我们新添加的BsdSocket类,就把LwIPsocket接口重新封装了一层,然后注册到eCLR内核中:

BsdSocket* pBsdSocket = new BsdSocket();    

if (pBsdSocket != NULL)

{

if (pBsdSocket->Open(BsdSocket::Stream, CRemotingDeamon::ClrServerPort) == true)

{           

CRemotingDeamon::addSocket(pBsdSocket);        

}        

pBsdSocket->release();      

}

封装代码太多,不方便贴出来,总之都是那些Open, Close, Send, Recv, Select等接口的二次封装,没有什么技术含量,大家简单理解下就可以了。

 

6. 系统初始化与调度实现

最后就是我们eCLR内核初始化还有调度了。eCLR内核初始化大家应该好理解,就是调用eCLRAPI把内核启动起来,eCLR内核还需要调度?这里的调度的概念大家需要先明白一点,MULTIPROG中可以定义多个任务,这些任务主要用到的是Cyclic以及Default类型,而这些任务之所以可以受eCLR内核控制来执行,都是需要有一个高优先级的FreeRTOS单独任务来进行整体调度,例如下面这张图片所示例的PLC任务在eCLR内核中执行的方式:

 

2.7

 

我们需要一个单独的任务来进行eCLR内核的调度,调度实现也非常简单,创建一个任务来执行eclr_sched_task作为入口函数就可以了:

void eclr_sched_task(void* param)

{

  portTickType xLastWakeTime;

  

  xLastWakeTime = xTaskGetTickCount();

  for( ;; )

  {

    vTaskDelayUntil( &xLastWakeTime, ( portTickType ) 1 );

    ClrController::Process();

  }

}

Cortex-M4 100MHZ以上的MCU里,我们一般都把eCLR调度周期修改为1msFreeRTOS的心跳时间保持一致,这里不使用MiCOMiCO_thread_msleep实现延时1ms也是有原因的,因为ClrController::Process();执行也会消耗少许CPU事件,如果使用MiCO_thread_msleep那么实际一个完整的调度加1ms延时,总时间会稍稍大于1ms,这样eCLR内核时钟就会有少许偏差,所以我们要保证ClrController::Process()严格1ms调用一次。

 

在今天这个章节,我们的eCLR内核移植过程就结束了,希望对大家能够有所启发,因为eCLRMULTIPROG都不是开源免费的软件,所以更多的大家是看看我这边进行一些总结和大体思路的讲解。在整个项目完结后我尽量向老板申请可以给出一个评估版的镜像文件以及MULTIPROG评估版开放给大家玩玩,虽然现在还不确定最后结果,但是我会尽量争取,今天就到这里,我们下期见。


如果大家喜欢我的文章,可以扫描下面二维码关注我的个人公众号<<IoT物联世界>>来获得第一时间的更新以及我的最新动态哦:


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值