开源PLC学习笔记04(再从51开始 通讯)——2013_11_9
本帖最后由 oldbeginner 于 2013-11-9 16:39 编辑在学习笔记03中浏览了一下协议,因为协议看起来很枯燥,直接进入到代码中去,边理解代码边学习协议,选择了Read code(修改为:读程序模块)作为突破口,虽然费了很多精力,但是还是值得的,现在可以再回到协议中去。再次引用一下,重复对初学者是必要的。
http://www.amobbs.com/thread-3394474-1-1.html
-------------------------------------------------------------------------------------------
RS232C接口,通讯波特率 9600,7,e,1 。
PC机(设置为FX1N)向停止运行的FX1N正常下载3步程序。
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0011, 数据: STX,"0","0E02","02",ETX,"6C"// 查询PLC 0E02H地址数据字(PLC型号)
PLC应答 字节数: 0008, 数据: STX,"62","66",ETX,"D7" // PLC返回"6266",代表PLC型号FX1N
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0013, 数据: STX,"E00","01C0","01",ETX,"DD" // 查询PLC 01C0H地址数据字节(FX1N运行状态)
PLC应答 字节数: 0006, 数据: STX,"0A",ETX,"74" // PLC返回"0A",代表PLC暂停
// 如PLC返回"09",代表PLC运行
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0011, 数据: STX,"0","0E02","02",ETX,"6C"// 再次查询PLC 0E02H地址数据字(PLC型号)
PLC应答 字节数: 0008, 数据: STX,"62","66",ETX,"D7" // PLC返回"6266",代表PLC型号FX1N
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0013, 数据: STX,RTC,"8000","2E",ETX,"E8" // 发出读PLC 8000H地址处连续2EH字节数据指令(这些内容为PLC内预置参数值)
PLC应答 字节数: 0008, 数据: STX,'0','8','0','0','D','7','C' // PC机读入PLC 8000H地址处连续2EH字节数据
PLC应答 字节数: 0008, 数据: '9','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0','2','0','2'
PLC应答 字节数: 0008, 数据: '0','2','0','2','0',ETX,"CE"
PC机发送 字节数: 0013, 数据: STX,"E01","802E","2E",ETX,"FF"// 发出读PLC 802EH地址处连续2EH字节数据指令(这些内容为PLC内预置参数值)
PLC应答 字节数: 0008, 数据: STX,'2','0','2','0','F','4','0' // PC机读入PLC 802EH地址处连续2EH字节数据
PLC应答 字节数: 0008, 数据: '9','F','F','0','B','F','4','0'
PLC应答 字节数: 0008, 数据: '1','E','7','0','3','6','4','0'
PLC应答 字节数: 0008, 数据: 'E','C','7','0','E','D','C','0'
PLC应答 字节数: 0008, 数据: 'E','F','F','0','E','9','0','0'
PLC应答 字节数: 0008, 数据: '1','F','E','0','3','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0','0','0','0'
PLC应答 字节数: 0008, 数据: '0','0','0','0','0',ETX,"E5"
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0013, 数据: STX,"E00","0E06","02",ETX,"E5" // 发出读PLC 0E06H地址处一字数据指令(这些内容为PLC内预置参数值)
PLC应答 字节数: 0008, 数据: STX,"1000",ETX,"C4" // PC机读入PLC 0E06H地址处一字数据
-------------------------------------------------------------------------------------------
// 读入PLC中已有程序
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0013, 数据: STX,"E01","805C","2E",ETX,"00"// 发出读PLC 805CH地址处连续2EH字节数据指令
PLC应答 字节数: 0008, 数据: STX,'0','2','2','4','0','3','C' // PC机读入PLC 805CH地址处连续2EH字节数据
PLC应答 字节数: 0008, 数据: '5','0','F','0','0','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F','F','F','F'
PLC应答 字节数: 0008, 数据: 'F','F','F','F','F',ETX,"5C"
-------------------------------------------------------------------------------------------
// 读入结束
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0010, 数据: STX,'E','7','7','6','0','E',ETX,"61"
PLC应答 字节数: 0001, 数据: ACK
-------------------------------------------------------------------------------------------
// 下载程序
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0025, 数据: STX,"E11","805C","06",'0','2','2','4','0','3','C','5','0','F','0','0',ETX,"69"
// PC机发出写PLC 805CH地址处连续06H字节(3步程序)数据指令
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
-------------------------------------------------------------------------------------------
// 下载结束
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ // PC机发出通信请求
PLC应答 字节数: 0001, 数据: ACK // PLC应答(已收到)
PC机发送 字节数: 0010, 数据: STX,'E','8','7','6','0','E',ETX,"62"
PLC应答 字节数: 0001, 数据: ACK
PC机发送 字节数: 0001, 数据: ENQ
PLC应答 字节数: 0001, 数据: ACK
PC机发送 字节数: 00ENQ, 数据: STX,"B",ETX,"45"
PLC应答 字节数: 0001, 数据: ACK
-------------------------------------------------------------------------------------------
// 检验程序
-------------------------------------------------------------------------------------------
PC机发送 字节数: 0001, 数据: ENQ
PLC应答 字节数: 0001, 数据: ACK
PC机发送 字节数: 0013, 数据: STX,"E01","805C","06",ETX,"EF" // 发出读PLC 805CH地址处连续06H字节数据指令
PLC应答 字节数: 0008, 数据: STX,'0','2','2','4','0','3','C','5','0','F','0','0',ETX,"7C"
// PC机读入PLC 805CH地址处连续06字节数据
-------------------------------------------------------------------------------------------
// 结束下载
-------------------------------------------------------------------------------------------
本帖最后由 oldbeginner 于 2013-11-9 16:44 编辑
// 函数名称: FX1NProcessing
//FX1N.c
// 功能描述:解析接收缓冲区中的数据
使用到了
扩展命令码
读配置 "E00"
写配置 "E10"
读程序 "E01"
写程序 "E11"
该函数就是对上述协议的代码翻译。
例如,笔记03就是围绕其中的Read code展开的,展开了一个,其余的模块也是同样道理。
会接触的几个常用模块如下,
(还有其它模块)
可以看出,不同模块前2步是一样。
1、PC机发出通信请求
2、PLC应答
从第3步开始不一致,这也是FX1NProcessing为什么有大量if elseif结构的原因。
根据第3步接收到的数据,FX1NProcessing要判断调用哪个模块。
本帖最后由 oldbeginner 于 2013-11-9 17:54 编辑
oldbeginner 发表于 2013-11-9 15:15 static/image/common/back.gif
// 函数名称: FX1NProcessing
//FX1N.c
// 功能描述:解析接收缓冲区中的数据
回到FX1NProcssing函数中(为了简洁UartReceiveBuffer都写为Buffer)
if(Buffer==ENQ) // 发送请求 ==== 1. 3. 5. 7. 10. 12. 14.(14开始下发数据)
{
UartSendchar(ACK);
}
else if((Buffer==STX)&(Buffer==ETX)) // 可识别的
{
各种模块;
}
这里简化了一下函数结构,步骤1和2就是通过下面代码响应,
if(Buffer==ENQ) // 发送请求 ==== 1. 3. 5. 7. 10. 12. 14.(14开始下发数据)
{
UartSendchar(ACK);
}
而其它模块的调用,是通过下面代码实现
else if((Buffer==STX)&(Buffer==ETX)) // 可识别的
{
各种模块;
}
再复习一下报文格式,
看到报文结构(CMD在Read code中对应3个ASCII码),UartReceiveCounter也能猜出是什么了。
//接收计数器 定义在uart.c中
volatile unsigned char UartReceiveCounter=0;
再复习一下:
扩展命令码
读配置 "E00"
写配置 "E10"
读程序 (Read code)"E01"
写程序 (Write code) "E11"
****************************************************
在各种模块代码中,
//==== Read Code(CMD在Read code中对应3个ASCII码)
if((Buffer==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x31))
{
Read code代码;
}
即如果Buffer="E",Buffer="0",Buffer="1"。Read code的CMD是"E01"。
*******************************************************************
继续看其它模块执行的条件判断,
//==== Write Code ========================
else if((Buffer==0x45)\
&&(Buffer ==0x31)\
&&(Buffer ==0x31))
{
Write code代码;
}
即如果Buffer="E",Buffer="1",Buffer="1"。Wirte code的CMD是"E11"。
**********************************************************************
//PC :02 30 30 45 30 32 30 32 03 36 43 ==== 2. 6.
//MCU :02 36 32 36 36 03 44 37
// 查询PLC 类型:FX1N\FX1S
else if((Buffer==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32))
{
UartSendByte((unsigned char *)OrderSend1,8);
}
即如果Buffer="00E0202",则是查询PLC类型。
***********************************************************************
//PC :02 45 30 30 30 31 43 30 30 31 03 44 44 ==== 4.
//MCU :02 30 41 03 37 34
// 查询PLC 当前状态:暂停\运行
else if((Buffer==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x31)\
&&(Buffer ==0x43)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x31))
{
UartSendByte((unsigned char *)OrderSend2,6);
}
即如果Buffer="E0001C001",则是查询PLC类型。
*************************************************************************
//PC :02 45 30 30 30 45 30 36 30 32 03 45 35 ==== 11.
//MCU :02 31 30 30 30 03 43 34
else if((Buffer==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x36)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32))
{
UartSendByte((unsigned char *)OrderSend5,8);
}
即如果Buffer="E000E0602",则是什么?只注释个11,表示什么?先继续下去,看能否自动出现解释。
**************************************************************************************
//PC :02 45 30 30 30 45 43 43 30 32 03 30 35 ==== 13.
//MCU :02 30 38 30 30 03 43 42
else if((Buffer==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45)\
&&(Buffer ==0x43)\
&&(Buffer ==0x43)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32))
{
UartSendByte((unsigned char *)OrderSend6,8);
}
即如果Buffer="E000ECC02",则是什么?只注释个13,表示什么?先继续下去,看能否自动出现解释。
**************************************************************************************
//PC :02 45 37 37 36 30 45 03 36 31
//MCU :06
else if((Buffer==0x45)\
&&(Buffer ==0x37)\
&&(Buffer ==0x37)\
&&(Buffer ==0x36)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45))
{
UartSendchar(ACK);
}
即如果Buffer="E7760E",还是继续
***************************************************************************************
//PC :02 45 38 37 36 30 45 03 36 32
//MCU :06
else if((Buffer==0x45)\
&&(Buffer ==0x38)\
&&(Buffer ==0x37)\
&&(Buffer ==0x36)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45))
{
UartSendchar(ACK);
}
继续
******************************************************
//PC :02 42 03 34 35
//MCU :06
else if((Buffer==0x42))
{
UartSendchar(ACK);
}
继续
******************************************************
//PC :02 45 36 31 03 41 46 ;02 45 36 30 03 41 45 ;02 45 36 33 03 42 31 ;02 45 36 32 03 42 30 ---清除PLC存储空间[顺序]
//MCU :06
else if((Buffer==0x45)\
&&(Buffer ==0x36)\
&&(Buffer <0x34)) // 暂定,可以实现目的,但是有待于进一步讨论.
{
ErasurePLC(ErasureCODE); // 擦除PLC程序
UartSendchar(ACK);
}
*****************************************************
至此,FX1NProcessing结构框架一清二楚了。
补充:else if((Buffer==STX)&(Buffer==ETX)) // 可识别的
&是否改为&&,感觉结果应该不变。http://zhidao.baidu.com/link?url=ZbGLo8DOS1WMEvkFMwEhVzLauxXXfbo_PHQoig-InAuhgSLChxPnPB7hhAU-Wr1mhYpHeHLlTm-eXpZSTC6RT_ 不错,楼主加油 不错,楼主加油
{:victory:} 本帖最后由 oldbeginner 于 2013-11-9 21:10 编辑
oldbeginner 发表于 2013-11-9 16:27 static/image/common/back.gif
回到FX1NProcssing函数中(为了简洁UartReceiveBuffer都写为Buffer)
if(Buffer==ENQ ...
在结构清楚,协议更加熟悉的情况下,需要继续返回到Read code代码中进行第2遍了解,当然会快很多。
这次的重点在于第三部分,前2部分在第一遍理解时基本顺利。
***************************************************
第1遍理解时的难点
***************************************************
for(i=1;i<=ReadLen;i++)
{
Readdat=IAPFlashReadMode(ReadAddr+i-1); // 读FlashData
。。。。。
}
当时把i=1带入,看看结果
Readdat=IAPFlashReadMode(ReadAddr+i-1);
即Readdat=IAPFlashReadMode(ReadAddr);
然后调用IAP函数
IFADRH=ReadAddr>>8; // 这个字节必须在IAP存储区
IFADRL=ReadAddr;
然后
Readdat=IFD;
首先,Read code的功能是读程序,要看这个注释
// 编写读写代码须知
// 1.8000 以上地址与 PLC程序相关;
// 2.4000以上地址与 PLC寄存器[数据寄存器0x4000~0x7e40,31.125K (HEX).特殊寄存器0x0e00~0x0ec0,384字节 (HEX)]相关
其中
sfr IFD = 0xE2;
sfr IFADRH = 0xE3;
sfr IFADRL = 0xE4;
根据字面意思来帮助理解,IF就是IAP FLASH的缩写,所以IFADRH是IAP FLASH地址高8位,IFADRL是低8位,IFD就是对应的IAP FLASH数据(即程序)。
http://www.amobbs.com/data/attachment/forum/201311/09/155851yczueyoicjixcy1z.jpg.thumb.jpg
读取元件的地址是通过ReadAddr传递给IAPFlashReadMode函数,然后返回读取的数值Readdat;通过for循环,完成ReadLen次读取。
IAP如何实现读取这里还将直接略过,这次主要是为了确认Read code确实是读出了IAP FLASH中的的程序,如何IAP实现将放在专门理解IAP时。
******************************************************************************
小结一下Read code功能模块
1、第一部分计算得到读取地址ReadAddr;
2、第二部分计算得到读取字节数ReadLen;
3、第三部分中的循环部分,读出了IAP FLASH中的程序并赋值给Readdat;
第三部分中的剩余部分,发送ETX和检验和。
校验和理解上不难,但是写在程序里代码就有点啰嗦,为了简洁所以故意忽略。
oldbeginner 发表于 2013-11-9 17:43 static/image/common/back.gif
在结构清楚,协议更加熟悉的情况下,需要继续返回到Read code代码中进行第2遍了解,当然会快很多。
这次 ...
Write code由3部分组成,
1、计算得到写入字节数,
2、计算得到写入地址,
3、先擦Flash,然后再写入
***********************************************************
// 1、计算得到写入字节数:
for(i=8;i<10;i++)
Buffer=ascto0F(Buffer)
Buffer<<=4;
WriteLen=(Buffer+Buffer);
第一部分很好理解,Buffer是字节的高8位,Buffer是低8位。
************************************************************
// 2、计算得到写入地址:
for(i=4;i<8;i++)
Buffer=ascto0F(Buffer);
WriteAddr =Buffer<<12;
WriteAddr+=Buffer<<8;
WriteAddr+=Buffer<<4;
WriteAddr+=Buffer;
现在第二部分也很容易理解了,和Read code代码一模一样
*************************************************************
3、先擦Flash,然后再写入
if(WriteAddr==0x8000)
ErasurePLC(ErasureCODE); // 擦除PLC程序区
WriteFlash(WriteAddr,(unsigned char *)(Buffer+10),(unsigned char)WriteLen); // 写 flash
UartSendchar(ACK);
先查看定义(FX1N.h)
#define ErasureALL 0
#define ErasureCODE 1
则要理解
if(WriteAddr==0x8000)
ErasurePLC(1);
ErasurePLC是一个有点复杂的函数,而且涉及到IAP FLASH处理,这里不再展开,只是把这个函数的功能暂时记下,
1、擦除IAP缓存备份空间;
2、备份PLC 0x8000~0x805c 地址的数据
3、擦除PLC程序空间
4、还原PLC 0x8000~0x805c 地址的数据
然后就是WriteFlash函数,这个函数代码不长,但是它调用了一个比较长的函数,而且这个函数注释说该函数需要修改。
暂时勉强认为WriteFlash把程序写入到Flash中,等IAP入门后再回来确认这些不清楚的地方。
********************************************************************
FX1NProcessing函数中两个比较重要的函数就先这样理解。
下一步再把和FX1NProcssing相关但不和IAP相关的函数整理一下,就进入IAP入门。
不错,楼主加油
oldbeginner 发表于 2013-11-9 21:10 static/image/common/back.gif
Write code由3部分组成,
1、计算得到写入字节数,
2、计算得到写入地址,
Write code功能后,就是查询PLC类型功能,代码如下
***********************************************************
//PC :02 30 30 45 30 32 30 32 03 36 43 ==== 2. 6.
//MCU :02 36 32 36 36 03 44 37
// 查询PLC 类型:FX1N\FX1S
else if((UartReceiveBuffer==0x30)\
&&(UartReceiveBuffer ==0x30)\
&&(UartReceiveBuffer ==0x45)\
&&(UartReceiveBuffer ==0x30)\
&&(UartReceiveBuffer ==0x32)\
&&(UartReceiveBuffer ==0x30)\
&&(UartReceiveBuffer ==0x32))
{
UartSendByte((unsigned char *)OrderSend1,8);
}
*************************************************************
UartSendByte根据字面意思理解,就是通过串口发送字节。因为代码简单,直接放在这里理解。
// 函数名称: UartSendByte
//uart.c
// 功能描述:发送一串数据
//
// 输 入: unsigned char *Startaddr,unsigned char SendByte
//
// 输 出: void
void UartSendByte(unsigned char *Startaddr,unsigned char SendByte)
{
while(SendByte--)
{
UartSendchar(*Startaddr++);
}
}
而OrderSend1是预先存储好的数据,需要的时候直接使用。
unsigned char code OrderSend1[]={0x02,0x36,0x32,0x36,0x36,0x03,0x44,0x37}; // '6266'PLC型号 FX1N;'C256'PLC型号 FX1S.
SendByte 8 7 6 5 4 3 2 1
*Startaddr 0x02, 0x36, 0x32, 0x36, 0x36, 0x03, 0x44, 0x37
这个函数还是比较好理解的。
****************************************************************
而类似OrderSend1的还有几个,而且它们的存储空间和IAP相关,这里直接列出,
//=====================================================
// IAP 空间分配如下:(31K) ----ASCII转HEX后存储.
// 1.IAP起始页 0x8000 ~ 0xbfff 存储PLC程序 -- 对应PLC 的0x8000~0xbfff 地址共8000步程序空间
// 2.IAP最后一页0 地址 存储 PLC类型标识核实标志 共 1 个字节
// 3.IAP最后一页1 ~ PLCTypeArrayLenMAX 存储 PLC类型标识 共 PLCTypeArrayLen 个字节 PLCTypeArrayLen
// 4.IAP倒数第二页 缓存空间 不许被其他数据占用!
// 5.其他 保留
//===============================================
unsigned char code OrderSend1[]={0x02,0x36,0x32,0x36,0x36,0x03,0x44,0x37}; // '6266'PLC型号 FX1N;'C256'PLC型号 FX1S.
unsigned char code OrderSend2[]={0x02,0x30,0x41,0x03,0x37,0x34}; // '0A'PLC 暂停;'09'PLC 运行
//********************************************************************************************//
//* *//
//* 总结说明: *//
//* 1.OrderSend3为 PLC 0x8000 地址开始,存储了 0x2e 个字节的数据; *//
//* 2.其中从 0x8008地址开始的 0x08 个字节的数据,为PLC存储器相关代码,具体实意未知. *//
//* *//
//********************************************************************************************//
unsigned char code OrderSend3[]=
{0x08,0x00,0xDE,0xCB,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20
,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20};
//********************************************************************************************//
//* *//
//* 总结说明: *//
//* 1.OrderSend4为 PLC 0x802e 地址开始,存储了 0x2e 个字节的数据; *//
//* 2.其中从 0x8048地址开始的 0x04 个字节的数据,为PLC[文件寄存器]相关代码,具体实意未知. *//
//* *//
//********************************************************************************************//
unsigned char code OrderSend4[]=
{0x20,0x20,0xF4,0x09,0xFF,0x0B,0xF4,0x01,0xE7,0x03,0x64,0x0E,0xC7,0x0E,0xDC,0x0E,0xFF,0x0E,0x90,0x01,0xFE,0x03,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
unsigned char code OrderSend5[]={0x02,0x31,0x30,0x30,0x30,0x03,0x43,0x34}; // PLC存储器相关代码
unsigned char code OrderSend6[]={0x02,0x30,0x38,0x30,0x30,0x03,0x43,0x42};
目前只需要知道这些数组是预先设置好的,具体怎样存储到IAP FLASH暂时搁置。
***********************************************************************************
本帖最后由 oldbeginner 于 2013-11-10 11:24 编辑
oldbeginner 发表于 2013-11-10 10:42 static/image/common/back.gif
Write code功能后,就是查询PLC类型功能,代码如下
************************************************** ...
现在要做的是简写FX1NProcessing函数,先看一下简写后的效果,复制到word上不到3页,字符数(含注释)约2000个,对我这样的初学者感觉可读性好多了。
*****************************************************************
void FX1NProcessing(void)
{
unsigned char sumdata=0;
if(UartDataReadyFlag) // 有数据来
{
UartDataReadyFlag=0;
if(Buffer==ENQ) // 发送请求 ==== 1. 3. 5. 7. 10. 12. 14.(14开始下发数据)
{
UartSendchar(ACK);
}
else if(STXETX) // 可识别的
{
//==================== 核查校验码 sumdata ========================
sumdata=asctohex((unsigned char *)(Buffer+UartReceiveCounter-2));
//==========================================================
if(sumdata==sumcheck((unsigned char *)(Buffer+1),(UartReceiveCounter-3)))
{
//= Read Code =============
if(E01)
{
//读程序
ReadCode();
}
//= Write Code ==============
else if(E11)
{
//写程序
WriteCode();
}
// 查询PLC 类型:FX1N\FX1S
else if(E00E0202)
{
UartSendByte((unsigned char *)OrderSend1,8);
}
// 查询PLC 当前状态:暂停\运行
else if(E0001C001)
{
UartSendByte((unsigned char *)OrderSend2,6);
}
else if(E000E0602)
{
UartSendByte((unsigned char *)OrderSend5,8);
}
else if(E000ECC02)
{
UartSendByte((unsigned char *)OrderSend6,8);
}
else if(E7760E)
{
UartSendchar(ACK);
}
else if(E8760E)
{
UartSendchar(ACK);
}
else if((Buffer==0x42))
{
UartSendchar(ACK);
}
//PC :02 45 36 31 03 41 46 ;02 45 36 30 03 41 45 ;02 45 36 33 03 42 31 ;02 45 36 32 03 42 30 ---清除PLC存储空间[顺序]
//MCU :06
else if(E64) // 暂定,可以实现目的,但是有待于进一步讨论.
{
ErasurePLC(ErasureCODE); // 擦除PLC程序
UartSendchar(ACK);
}
//************* 有待于加入运行\中止相关代码 *******************
//************* 有待于加入监控测试 相关代码 *******************
}
else UartSendchar(NACK);
}
else UartSendchar(NACK);
UartReceiveCounter=0;
REN=1; // 使能接收
}
}
***********************************************************************************
首先很多&& && &&这样的判断都在FX1N.h中定义成宏,上面代码中括号中蓝色***就是相应宏名。另外把Read code和Write code功能写成函数了。
例如,
#define E00E0202 (Buffer==0x30)\
&&(Buffer ==0x30)\
&&(Buffer ==0x45)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32)\
&&(Buffer ==0x30)\
&&(Buffer ==0x32)
*****************************************
把PLC通讯协议做成宏,一方面可以提高可读性,另一方面修改或扩展也比较容易定位。 楼主,看来也楼主学习一下才行! oldbeginner 发表于 2013-11-10 11:14 static/image/common/back.gif
现在要做的是简写FX1NProcessing函数,先看一下简写后的效果,复制到word上不到3页,字符数(含注释)约20 ...
FX1NProcessing里的内容很多,因为FX1NProcessing主要处理通讯协议,所以有大量的串口函数包含在里面,在理解的时候,并没有研究相关串口函数,而是直接认为其功能是可以实现的,这样能把精力放在协议上。鉴于协议已经理清了思路,现在回过头来确认这些串口函数。
对uart相关函数的理解放在笔记05中。
http://www.amobbs.com/thread-5558494-1-1.html
大牛啊
本帖最后由 oldbeginner 于 2013-11-15 19:14 编辑
oldbeginner 发表于 2013-11-10 15:32 static/image/common/back.gif
FX1NProcessing里的内容很多,因为FX1NProcessing主要处理通讯协议,所以有大量的串口函数包含在里面,在 ...
********************************
托尔斯泰说,幸福的家庭都是相似的,不幸的家庭各有各的不幸原因。
移植到PLC上就是,能运行的PLC程序都是健壮的,不能运行的PLC程序有各种不同的bug或短板。
补充背景,
在后面的学习中,发现ASCII码扮演着非常重要的角色,而且很容易和16进制混淆,这是我理解PLC的一个短板,现在要回过头补习一下。
**********************************
网上搜了一些资料,但是不够直观,还是从代码入手来帮助理解。
在FX1N.c中有3个相关函数,实现功能如下,
1、功能描述:HEX 转 ASCII
2、功能描述:ASCII 转 HEX
3、功能描述:将ASCII 转换成0~F
先学习第一个函数
// 函数名称: hextoasc
//
// 功能描述:HEX 转 ASCII
//
// 输 入:unsigned char hexdata
//
// 输 出:unsigned int
******************************************
unsigned int hextoasc(unsigned char hexdata)
{
unsigned char ucdata=0;
unsigned intbackdata=0;
unsigned inttempdata=0;
ucdata = hexdata & 0x0f;
backdata = (ucdata<10)?(ucdata+0x30):(ucdata+0x41-0x0a);
hexdata>>= 4;
ucdata = hexdata & 0x0f;
tempdata = (ucdata<10)?(ucdata+0x30):(ucdata+0x41-0x0a);
tempdata <<= 8;
backdata|= tempdata;
return backdata;
}
换成自己理解的思路,
1、unsigned char ucdata=0; 定义1个无符号数;
2、ucdata = hexdata & 0x0f;
hexdata也是1个无符号数,
在这里,需要搜一下资料再理解,
a、字符型: 在内存中字符的存储实际上是把字符相对应的ASCII代码放到存储单元中的。而这些ASCII代码值在计算机中也是以二进制形式存放的。这个与整型的存储很相似。http://zhidao.baidu.com/link?url ... l3qtDf-hi9u1p6ejbQK
b、在C语言中字符和整型是通用的
如char c=65;和char c='A'都可以,实际上'A'赋值给c,是把它的asc||(也就是65)码赋给变量c的。 你就把字符型当做整型的另一种表示形式就行了http://zhidao.baidu.com/link?url ... gqRaXdbILGnd8lJ4VqK
比较抽象,假设
hexdata=0x32 (字符‘2’),
则 ucdata= 0x32 & 0x0F =0x02。
3、backdata = (ucdata<10)?(ucdata+0x30):(ucdata+0x41-0x0a);
此时ucdata=0x02,变成10进制就是2
backdata=ucdata + 0x30 = 0x02 + 0x30 =0x32
因为backdata是 unsigned int类型,16位,所以实际值 backdata= 0x0032
4、hexdata>>= 4;
此时 hexdata=0x03
5、 ucdata = hexdata & 0x0f;
则 ucdata= 0x03 & 0x0F =0x03。
6、 tempdata = (ucdata<10)?(ucdata+0x30):(ucdata+0x41-0x0a);
tempdata=ucdata+0x30=0x03+0x30=0x33
因为tempdata是 unsigned int类型,16位,所以实际值 tempdata= 0x0033
7、 tempdata <<= 8;
tempdata=0x3300
8、 backdata|= tempdata;
backdata= 0x0032 | 0x3300 = 0x3332
9、 return backdata;
return 0x3332
至此,稍微了解了,感觉很绕,首先找到字符'2'的ascii码 0x32 ,然后再找到 3的ascii和2的ascii码,合并成一个 unsigned int 返回。
为什么要这样复杂,要理解还需要看这个函数是如何应用的。使用到这个 HEX 转 ASCII函数的是FX1NProcessing中的Read code函数,好像就要第3遍回顾Read code函数了。
再进入ReadCode函数中(原FX1NProcessing函数中并没有,没有可读性,把Read code功能另外写成了一个函数)
忽略不相干后的函数表达如下,
void ReadCode(void)
{
计算得到读取地址:
计算得到读取字节数:
计算 sumdata 并发送读取字节
unsigned inttempascdata=0;
for(i=1;i<=ReadLen;i++)
{
Readdat=IAPFlashReadMode(ReadAddr+i-1); // 读FlashData
tempascdata=hextoasc(Readdat);
。。。。。
}
。。。
}
首先,hextoasc的输入变量是Readdat,Readdat是由IAP函数返回的,
根据对hextoasc函数功能的理解,Readdat应该是一个字符的ascii码,比如’2‘的ascii码,0x32。
此时,tempascdata=hextoasc(Readdat);
因为假设了 Readdat='2'
此时tempascdata=0x3332
然后再把tempascdata分为两部分发送,先发送0x33,在发送0x32。
再回顾一下笔记03
和上图是一致的,
要实现什么样的功能以及怎样实现已经理解了,但是觉得这种思路有些怪(可能刚接触这类变换的原因),本质上就是把1个ascii码变成2个ascii,就是把1个ascii码的两个16进制数,分别再变成各自的ascii码。
本帖最后由 oldbeginner 于 2013-11-15 17:07 编辑
oldbeginner 发表于 2013-11-15 15:42 static/image/common/back.gif
再进入ReadCode函数中(原FX1NProcessing函数中并没有,没有可读性,把Read code功能另外写成了一个函数) ...
既然hex可以转ascii,现在了解一下ascii转hex,感觉应该是可逆关系
// 函数名称: asctohex
//
// 功能描述:ASCII 转 HEX
//
// 输 入:unsigned char *ascdata
//
// 输 出:unsigned char
unsigned char asctohex(unsigned char *ascdata)
{
unsigned char backdata=0;
unsigned char tempdata=0;
if(*ascdata<0x3a)
backdata = *ascdata-0x30;
else if(*ascdata>0x40)
backdata = *ascdata-0x41+0x0a;
if(*(ascdata+1)<0x3a)
tempdata = *(ascdata+1)-0x30;
else if(*(ascdata+1)>0x40)
tempdata = *(ascdata+1)-0x41+0x0a;
backdata <<= 4;
backdata|= tempdata;
return backdata;
}
***********************************
1、输入变量是指向ascii码的指针,ascdata
2、如果该ascii码小于0x3a,则减去0x30,
例如'3',0x33<0x3a,然后减去0x30,得0x03
3、如果该ascii码大于0x40,则减去0x41再加上0x0a
例如'b',0x42>0x40,0x42 - 0x41 + 0x0a = 0x0b
4、再对 *(ascdata+1)作同样分析
例如’2‘,则得0x02
如果*ascdata='3',*(ascdata+1)='2',则
0x03<<4 | 0x02
即 0x32
是可逆的
*********************************************
使用到了asctohex函数的是FX1NProcessing的WriteCode函数(也被分出来作为一个函数)
wrdata=asctohex((unsigned char *)(Buf+i));
IAPFlashProgremMode(WriteAddr++,wrdata);
wrdata是一个无符号字符型变量,asctohex是会读连续两个字节,所以i每次要加2,在笔记06IAP理解中,这个地方理解错了。
可以这样理解,在flash中,存储的是ascii码。
本帖最后由 oldbeginner 于 2013-11-15 17:08 编辑
oldbeginner 发表于 2013-11-15 16:05 static/image/common/back.gif
既然hex可以转ascii,现在了解一下ascii转hex,感觉应该是可逆关系
// 函数名称: asctohex
最后一个函数
// 函数名称: ascto0F
//
// 功能描述:将ASCII 转换成0~F
//
// 输 入:unsigned char ucdata
//
// 输 出:unsigned char
unsigned char ascto0F(unsigned char ucdata)
{
if(ucdata<0x3a)
ucdata=ucdata-0x30;
else if(ucdata>0x40)
ucdata=ucdata-0x41+0x0a;
else while(1); // 错误处理
return ucdata;
}
将ascii码转成相应16进制的0~F。
例如 '2' 转成 0x02
'B' 转成 0x0B
现在有点感觉了。 学习!! oldbeginner 发表于 2013-11-15 17:00
最后一个函数
// 函数名称: ascto0F
//
在笔记10中理解了文件管理后,把FX1N通讯处理和uart功能合并在一起,做一下测试。测试工具使用串口通讯工具,而不是梯形图软件,因为目前开源的通讯协议还不完整。目前,只测试程序结构是好的,至于要和梯形图通讯的完整协议还要暂时缓一下。
采用了另外一个相关文件,作为测试核对表,http://wenku.baidu.com/link?url=HKUGcy7CIsFhDOy9ru4cP3PEJAnGKJb8UkLnUS4270bdu2n8CT3P9Ta9Z9KAJJB1M8HHTe1mDN1mcAm1BCG9TdNa0w715KnYWp3WHgknOLe
学习了太强大了
页:
[1]