探讨使用STM32嵌入式汇编PLC指令表实现方法。
一直潜水,冒个泡,探讨下STM32使用嵌入式汇编PLC指令表实现方法,基本成功。我的思路实现办法如下:
1. PLC的资源变量定义为一个结构,绝对定位到内存中一个固定地址。
typedef struct
{
//----------------------------------------------------------------------//
//D_0x600-0x6FF (512Byte)
//----------------------------------------------------------------------//
vu16 usT_Cnt;
//----------------------------------------------------------------------//
//D_0x700-0x7FF (512Byte)
//----------------------------------------------------------------------//
vu16 usT_Set;
//----------------------------------------------------------------------//
//D_0x800-0x8FF (512Byte)
//----------------------------------------------------------------------//
vu32 ulS; // M0000 => S000-S1023
vu32 ulX; // M1024 => X000-X377
vu32 ulY; // M1280 => Y000-Y377
vu32 ulT; // M1536 => T000-T255
vu32 ulM; // M2048 => M000-M1535
vu32 ulC; // M3584 => C000-C255
//----------------------------------------------------------------------//
//D_0x900-0x9FF (512Byte)
//----------------------------------------------------------------------//
vu32 ulSL; // SXYTMC 前次值
vu32 ulXL;
vu32 ulYL;
vu32 ulTL;
vu32 ulML;
vu32 ulCL;
//----------------------------------------------------------------------//
//D_0xA00-0xAFF (512Byte)
//----------------------------------------------------------------------//
vu32 ulSP; // SXYTMC 上升沿
vu32 ulXP;
vu32 ulYP;
vu32 ulTP;
vu32 ulMP;
vu32 ulCP;
//----------------------------------------------------------------------//
//D_0xB00-0xBFF (512Byte)
//----------------------------------------------------------------------//
vu32 ulSF; // SXYTMC 下降沿
vu32 ulXF;
vu32 ulYF;
vu32 ulTF;
vu32 ulMF;
vu32 ulCF;
//----------------------------------------------------------------------//
//D_0xC00-0xCFF (512Byte)
//----------------------------------------------------------------------//
vu32 ulTS; // M1792 => TE00-TE255
vu32 ulCS; // M3840 => CE00-CE255
vu32 ulTR; // 定时器复位控制
vu32 ulCR; // 计数器复位控制
vu32 ulCE; // 计数器边沿选择位
vu32 ulX_IDR; // 输入引脚状态缓存
vu32 ulX_IMG; // 输入位映像
vu32 ulT_IMG; // 定时器输出位映像
//----------------------------------------------------------------------//
//D_0xD00-0DFF (512Byte)
//----------------------------------------------------------------------//
vu8 ucXFilterSet; // 输入去抖动计数器
vu8 ucXFilter; // 输入去抖动计数器
//----------------------------------------------------------------------//
//D_0xE00-0EFF (512Byte)
//----------------------------------------------------------------------//
vu16 usC_Cnt[(C_Size*32+56)]; // D2048->0x800
//----------------------------------------------------------------------//
//D_0xF00-0FFF (512Byte)
//----------------------------------------------------------------------//
vu16 usC_Set[(C_Size*32+56)]; // D 2360->0x938
//----------------------------------------------------------------------//
//D_0x1000-07FFF (4KByte)
//----------------------------------------------------------------------//
vu16 usD; //D
} PLC_TypeDef;
//---------------------------------------------------------------------------//
// 定义PLC数据区
//---------------------------------------------------------------------------//
PLC_TypeDef plc __attribute__(( at(0x20000800) ));
2. 资源结构变量既然已经绝对定位,就和内部工作寄存器(如GPIOA等)一样,有了固定的地址,对应位域也固定了,可以轻松计算出来,定位为位宏。
//-------------------------------------------------------//
// 位宏
//-------------------------------------------------------//
#define S(n) *(u32*)(0x22000000+0x0C00*8*4+n*4)
#define X(n) *(u32*)(0x22000000+0x0C80*8*4+n*4)
#define Y(n) *(u32*)(0x22000000+0x0CA0*8*4+n*4)
#define T(n) *(u32*)(0x22000000+0x0CC0*8*4+n*4)
#define M(n) *(u32*)(0x22000000+0x0CE0*8*4+n*4)
#define C(n) *(u32*)(0x22000000+0x0DA0*8*4+n*4)
#define SL(n) *(u32*)(0x22000000+0x0DC0*8*4+n*4)
#define XL(n) *(u32*)(0x22000000+0x0E40*8*4+n*4)
#define YL(n) *(u32*)(0x22000000+0x0E60*8*4+n*4)
#define TL(n) *(u32*)(0x22000000+0x0E80*8*4+n*4)
#define ML(n) *(u32*)(0x22000000+0x0EA0*8*4+n*4)
#define CL(n) *(u32*)(0x22000000+0x0F60*8*4+n*4)
#define SP(n) *(u32*)(0x22000000+0x0F80*8*4+n*4)
#define XP(n) *(u32*)(0x22000000+0x1000*8*4+n*4)
#define YP(n) *(u32*)(0x22000000+0x1020*8*4+n*4)
#define TP(n) *(u32*)(0x22000000+0x1040*8*4+n*4)
#define MP(n) *(u32*)(0x22000000+0x1060*8*4+n*4)
#define CP(n) *(u32*)(0x22000000+0x1120*8*4+n*4)
#define SF(n) *(u32*)(0x22000000+0x1140*8*4+n*4)
#define XF(n) *(u32*)(0x22000000+0x11C0*8*4+n*4)
#define YF(n) *(u32*)(0x22000000+0x11E0*8*4+n*4)
#define TF(n) *(u32*)(0x22000000+0x1200*8*4+n*4)
#define MF(n) *(u32*)(0x22000000+0x1220*8*4+n*4)
#define CF(n) *(u32*)(0x22000000+0x12E0*8*4+n*4)
#define TS(n) *(u32*)(0x22000000+0x1300*8*4+n*4)
#define CS(n) *(u32*)(0x22000000+0x1320*8*4+n*4)
#define TR(n) *(u32*)(0x22000000+0x1340*8*4+n*4)
#define CR(n) *(u32*)(0x22000000+0x1360*8*4+n*4)
#define CE(n) *(u32*)(0x22000000+0x1380*8*4+n*4)
#define X_IDR(n) *(u32*)(0x22000000+0x13A0*8*4+n*4)
#define X_IMG(n) *(u32*)(0x22000000+0x13C0*8*4+n*4)
#define T_IMG(n) *(u32*)(0x22000000+0x13E0*8*4+n*4)
3. 这时候,主程序可以使用位宏实现逻辑功能。比如
Y(0) = X(0);
if( X(1)) Y(1) = 1;
if( X(2)) Y(1) = 0;
4.按照堆栈实现PLC指令表原理,定义嵌入式汇编宏。
//===========================================================//
// 嵌入式汇编
//===========================================================//
__asm void START(u32* M)
{
// R3 = 0
MOV R3,#0
// R7 =位区基址
MOV R7, R0
BX lr
}
__asm void LD( u32* M )
{
// R3 压栈
PUSH {R3}
// R3 =R0
LDR R3,
BX lr
}
__asm void LDI( u32* M )
{
// R3 压栈
PUSH {R3}
// R3 =~R0
LDR R1,
MVN R3,R1
BX lr
}
__asm void AND( u32* M )
{
//R3 = ( R3 & R0 )
LDR R1,
AND R3, R1
BX lr
}
__asm void ANDI( u32* M )
{
//R3 = ( R3 & (!R0) )
LDR R1,
MVN R2,R1
AND R3,R2
BX lr
}
__asm void OR( u32* M )
{
//R3 = ( R3 | R0 )
LDR R1,
ORR R3,R1
BX lr
}
__asm void ORI( u32* M )
{
//R3 = ( R3 | (!R0) )
LDR R1,
MVN R2,R1
ORR R3,R2
BX lr
}
__asm void ANB( void )
{
//R1 出栈
POP {R1}
//R3 = ( R3 & R1 )
AND R3,R1
BX lr
}
__asm void ORB( void )
{
//R1 出栈
POP {R1}
//R3 = ( R3 | R1 )
ORR R3,R1
BX lr
}
__asm void OUT( u32* M )
{
//R1 出栈
POP {R1}
//*R0 = R3
STR R3,
BX lr
}
__asm void SBT( u32* M )
{
//R1 出栈
POP {R1}
//*R0 |= R3
LDR R1,
ORR R3,R1
STR R3,
BX lr
}
__asm void RST( u32* M )
{
//R1 出栈
POP {R1}
//*R0 &= (!R3)
LDR R1,
MVN R2,R1
ORR R3,R2
STR R3,
BX lr
}
5. PLC的指令执行循环里便可以用指令表执行逻辑动作了。
void PLC_Poll(void)
{
START(&X(0));
LD( &X(0) );
OUT( &Y(0));
LDI( &X(1) );
OUT( &Y(1));
}
6.指令执行效率,从编译的汇编代码看,效率非常高,指令执行速度应该是非常快的。
231: LD( &X(0) );
0x08000EE0 4807 LDR r0,; @0x08000F00
0x08000EE2 F7FFF955BL.W LD (0x08000190)
232: OUT( &Y(0));
233:
0x08000EE6 4807 LDR r0,; @0x08000F04
0x08000EE8 F7FFF976BL.W OUT (0x080001D8)
96: __asm void LD( u32* M )
97: {
98: // R3 压栈
0x0800018E 4770 BX lr
99: PUSH {R3}
100:
101: // R3 =R0
0x08000190 B408 PUSH {r3}
102: LDR R3,
103:
0x08000192 6803 LDR r3,
104: BX lr
105: }
180: __asm void OUT( u32* M )
181: {
182: //R1 出栈
0x080001D6 4770 BX lr
183: POP {R1}
184:
185: //*R0 = R3
0x080001D8 BC02 POP {r1}
186: STR R3,
187:
0x080001DA 6003 STR r3,
188: BX lr
189: }
LD/OUT实测实170ns, 按照72Mhz STM32 指令执行周期算,大致估计单周期基本指令执行速度应该在0.5us内
7. 硬件使用STM32103RBT6, 资源为14点入,10点出, 1RS232, 2RS485 , 1CAN ,1SPI ,2ADC, 1RTC,RS232可直接下载程序。板上设置了ISP(TTL)和JTAG接口。 1RS232, 2RS485 使用高速磁偶隔离,3000V DC/DC, 1CAN 可选另外一组3000V DC/DC ,高速磁偶隔离,也可不不隔离。 SPI不隔离。输入点位双向光耦。端子使用西门子PLC上使用的黑色可拔插端子。
http://cache.amobbs.com/bbs_upload782111/files_17/ourdev_464952.gif
224_CPU (原文件名:CPU.gif)
http://cache.amobbs.com/bbs_upload782111/files_17/ourdev_464953.gif
224_IO (原文件名:IO.gif)
代码仅供参考思路,已经在硬件测试功能可以实现。
另外问下阿莫,上次开源PLC项目时,上面的那个运行开关在那里买到,我怎么也买不到。就是5脚,单刀双掷,三个拨动位置的那种。
8. 思路延伸:优化汇编代码,使汇编代码简单、格式固定且可在任意位置运行,找到汇编指令和机器码中间的变化编译规律,就可以将指令表对应为机器码数组,然后做一个简单变量替换,就可以实现下位机编译,所以做一款下位机编译执行的PLC应该没有什么太大的技术问题。 好东西,该加裤子 壳哪的? 简易PLC 不错! 非常好的内容。领教了!像您学习!
不过汇编代码不容易移植,不知道大牛对这种汇编的代码有什么好的办法用来移植吗? 基本没办法移植,因为这些代码用了stm32的位域的特性,没有位域的mcu,肯定是无法移植的。
汇编实现plc指令,就有可能实现plc指令的下位机编译执行。在程序下载的同时,将plc程序在下位机里编译为汇编指令对应的机器码,我研究了一下机器码和汇编指令的关系,确定通过简单的替换机器码中操作数的方法,完全可以实现下位机编译。
虽然汇编实现plc内核速度应该是最快的,但是用汇编实现,也有许多缺点,比如实现实时的错误检测,和实现复杂的应用指令比较吃力。
所以,我后来实现了全部的下载监控等的通讯功能后,内核实现暂时也是以先用解释方式实现的。这样做工作量小多了。 回复【6楼】21ele
-----------------------------------------------------------------------
谢谢!
最近再拜读您的几篇文章,受益匪浅。也开始尝试设计一个简单的控制器。需要向您学习的地方还很多。
作为logic Engine这部分,是整个PLC控制的核心部分。也是各大厂商比拼速度的地方。
因为现在没有一块比较好的MCU可以胜任所有我们需要的PLC功能,所以我考虑的也是采用C写解释型的LOGIC ENGINE。
如果最终找到了一款比较适合做的,则对相应的部分进行代码的优化,就如您的方法,更改成汇编代码,除了用CORTEX-M3的位域外,还可以大量的使用内核寄存器进行操作,速度估计提升更多。当然并不是所有的代码都用汇编,只是基本的61131-3的规定的一些指令使用,复杂的仍旧使用C来完成您所说的容错,复杂控制等。 mark 嗯,以前用C函数来做PLC,速度没有优势,看了楼主的汇编型PLC,有了新的思路,把原来的PLC改下,提高运行速度,哈哈。 效率高 但会不会很繁琐? 好资料,值得学习。 标记,探讨使用STM32嵌入式汇编PLC指令表 非常精彩非常精彩 我知道,那个拨动开关产自上海的一个日本企业 学习了,谢谢分享! MARK,学习
页:
[1]