搜索
bottom↓
回复: 19

开源PLC学习笔记04(再从51开始 通讯)——2013_11_9

[复制链接]

出0入0汤圆

发表于 2013-11-9 14:57:16 | 显示全部楼层 |阅读模式
本帖最后由 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字节数据
-------------------------------------------------------------------------------------------
// 结束下载
-------------------------------------------------------------------------------------------

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

 楼主| 发表于 2013-11-9 15:15:21 | 显示全部楼层
本帖最后由 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要判断调用哪个模块。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-11-9 16:27:50 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-9 17:54 编辑
oldbeginner 发表于 2013-11-9 15:15
// 函数名称: FX1NProcessing
//FX1N.c
// 功能描述:  解析接收缓冲区中的数据


回到FX1NProcssing函数中(为了简洁UartReceiveBuffer都写为Buffer
                if(Buffer[0]==ENQ)        // 发送请求                                        ==== 1. 3. 5. 7. 10. 12. 14.(14开始下发数据)
                {
                        UartSendchar(ACK);
                }
                else if((Buffer[0]==STX)&(Buffer[UartReceiveCounter-3]==ETX))        // 可识别的
                {
                        各种模块;
                }

这里简化了一下函数结构,步骤1和2就是通过下面代码响应,
                if(Buffer[0]==ENQ)        // 发送请求                                        ==== 1. 3. 5. 7. 10. 12. 14.(14开始下发数据)
                {
                        UartSendchar(ACK);
                }

而其它模块的调用,是通过下面代码实现
                else if((Buffer[0]==STX)&(Buffer[UartReceiveCounter-3]==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[1]==0x45)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x31))
                  {
                       Read code代码;
                   }
即如果Buffer[1]="E",Buffer[2]="0",Buffer[3]="1"。Read code的CMD是"E01"。
*******************************************************************

继续看其它模块执行的条件判断,
                //==== Write Code ========================
                else if((Buffer[1]==0x45)\
                &&(Buffer[2] ==0x31)\
                &&(Buffer[3] ==0x31))
                  {
                       Write code代码;
                   }
即如果Buffer[1]="E",Buffer[2]="1",Buffer[3]="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[1]==0x30)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x45)\
                &&(Buffer[4] ==0x30)\
                &&(Buffer[5] ==0x32)\
                &&(Buffer[6] ==0x30)\
                &&(Buffer[7] ==0x32))                        
                {
                    UartSendByte((unsigned char *)OrderSend1,8);
                }
即如果Buffer[1:7]="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[1]==0x45)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x30)\
                &&(Buffer[4] ==0x30)\
                &&(Buffer[5] ==0x31)\
                &&(Buffer[6] ==0x43)\
                &&(Buffer[7] ==0x30)\
                &&(Buffer[8] ==0x30)\
                &&(Buffer[9] ==0x31))                        
                {
                    UartSendByte((unsigned char *)OrderSend2,6);
                }
即如果Buffer[1:9]="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[1]==0x45)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x30)\
                &&(Buffer[4] ==0x30)\
                &&(Buffer[5] ==0x45)\
                &&(Buffer[6] ==0x30)\
                &&(Buffer[7] ==0x36)\
                &&(Buffer[8] ==0x30)\
                &&(Buffer[9] ==0x32))
                {
                    UartSendByte((unsigned char *)OrderSend5,8);
                }
即如果Buffer[1:9]="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[1]==0x45)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x30)\
                &&(Buffer[4] ==0x30)\
                &&(Buffer[5] ==0x45)\
                &&(Buffer[6] ==0x43)\
                &&(Buffer[7] ==0x43)\
                &&(Buffer[8] ==0x30)\
                &&(Buffer[9] ==0x32))
                {
                    UartSendByte((unsigned char *)OrderSend6,8);
                }
即如果Buffer[1:9]="E000ECC02",则是什么?只注释个13,表示什么?先继续下去,看能否自动出现解释。
**************************************************************************************

                //PC    :02 45 37 37 36 30 45 03 36 31
                //MCU    :06
                else if((Buffer[1]==0x45)\
                &&(Buffer[2] ==0x37)\
                &&(Buffer[3] ==0x37)\
                &&(Buffer[4] ==0x36)\
                &&(Buffer[5] ==0x30)\
                &&(Buffer[6] ==0x45))
                {
                    UartSendchar(ACK);
                }
即如果Buffer[1:9]="E7760E",还是继续
***************************************************************************************

                //PC    :02 45 38 37 36 30 45 03 36 32
                //MCU    :06
                 else if((Buffer[1]==0x45)\
                &&(Buffer[2] ==0x38)\
                &&(Buffer[3] ==0x37)\
                &&(Buffer[4] ==0x36)\
                &&(Buffer[5] ==0x30)\
                &&(Buffer[6] ==0x45))
                {
                    UartSendchar(ACK);
                }
继续
******************************************************

                //PC    :02 42 03 34 35
                //MCU    :06
                else if((Buffer[1]==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[1]==0x45)\
                &&(Buffer[2] ==0x36)\
                &&(Buffer[3] <0x34))                //    暂定,可以实现目的,但是有待于进一步讨论.
                {
                    ErasurePLC(ErasureCODE);                //    擦除PLC程序
                    UartSendchar(ACK);
                }
*****************************************************

至此,FX1NProcessing结构框架一清二楚了。

补充:else if((Buffer[0]==STX)&(Buffer[UartReceiveCounter-3]==ETX))        // 可识别的
&是否改为&&,感觉结果应该不变。http://zhidao.baidu.com/link?url ... HeHLlTm-eXpZSTC6RT_

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2013-11-9 17:23:38 | 显示全部楼层
不错,楼主加油

出0入0汤圆

发表于 2013-11-9 17:31:18 | 显示全部楼层
不错,楼主加油

出0入0汤圆

 楼主| 发表于 2013-11-9 17:43:54 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-9 21:10 编辑
oldbeginner 发表于 2013-11-9 16:27
回到FX1NProcssing函数中(为了简洁UartReceiveBuffer都写为Buffer)
                if(Buffer[0]==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数据(即程序)。


读取元件的地址是通过ReadAddr传递给IAPFlashReadMode函数,然后返回读取的数值Readdat;通过for循环,完成ReadLen次读取。

IAP如何实现读取这里还将直接略过,这次主要是为了确认Read code确实是读出了IAP FLASH中的的程序,如何IAP实现将放在专门理解IAP时。
******************************************************************************
小结一下Read code功能模块
1、第一部分计算得到读取地址ReadAddr;
2、第二部分计算得到读取字节数ReadLen;
3、第三部分中的循环部分,读出了IAP FLASH中的程序并赋值给Readdat;
     第三部分中的剩余部分,发送ETX和检验和。

校验和理解上不难,但是写在程序里代码就有点啰嗦,为了简洁所以故意忽略。




出0入0汤圆

 楼主| 发表于 2013-11-9 21:10:03 | 显示全部楼层
oldbeginner 发表于 2013-11-9 17:43
在结构清楚,协议更加熟悉的情况下,需要继续返回到Read code代码中进行第2遍了解,当然会快很多。
这次 ...

Write code由3部分组成,
1、计算得到写入字节数,
2、计算得到写入地址,
3、先擦Flash,然后再写入
***********************************************************
                                       
//        1、计算得到写入字节数:
for(i=8;i<10;i++)
         Buffer=ascto0F(Buffer)

  Buffer[8]<<=4;

  WriteLen=(Buffer[8]+Buffer[9]);
第一部分很好理解,Buffer[8]是字节的高8位,Buffer[9]是低8位。

************************************************************

//    2、计算得到写入地址:
for(i=4;i<8;i++)
            Buffer=ascto0F(Buffer);

WriteAddr =Buffer[4]<<12;
WriteAddr+=Buffer[5]<<8;
  WriteAddr+=Buffer[6]<<4;
  WriteAddr+=Buffer[7];
现在第二部分也很容易理解了,和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入门。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2013-11-10 06:25:41 | 显示全部楼层
不错,楼主加油

出0入0汤圆

 楼主| 发表于 2013-11-10 10:42:36 | 显示全部楼层
oldbeginner 发表于 2013-11-9 21:10
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[1]==0x30)\
                &&(UartReceiveBuffer[2] ==0x30)\
                &&(UartReceiveBuffer[3] ==0x45)\
                &&(UartReceiveBuffer[4] ==0x30)\
                &&(UartReceiveBuffer[5] ==0x32)\
                &&(UartReceiveBuffer[6] ==0x30)\
                &&(UartReceiveBuffer[7] ==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暂时搁置。
***********************************************************************************



出0入0汤圆

 楼主| 发表于 2013-11-10 11:14:52 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-10 11:24 编辑
oldbeginner 发表于 2013-11-10 10:42
Write code功能后,就是查询PLC类型功能,代码如下
************************************************** ...

现在要做的是简写FX1NProcessing函数,先看一下简写后的效果,复制到word上不到3页,字符数(含注释)约2000个,对我这样的初学者感觉可读性好多了。

*****************************************************************
void FX1NProcessing(void)
{
        unsigned char sumdata=0;

        if(UartDataReadyFlag)                                // 有数据来
        {
                UartDataReadyFlag=0;
                if(Buffer[0]==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[1]==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[1]==0x30)\
                &&(Buffer[2] ==0x30)\
                &&(Buffer[3] ==0x45)\
                &&(Buffer[4] ==0x30)\
                &&(Buffer[5] ==0x32)\
                &&(Buffer[6] ==0x30)\
                &&(Buffer[7] ==0x32)
*****************************************
把PLC通讯协议做成宏,一方面可以提高可读性,另一方面修改或扩展也比较容易定位。

出0入8汤圆

发表于 2013-11-10 11:21:16 来自手机 | 显示全部楼层
楼主,看来也楼主学习一下才行!

出0入0汤圆

 楼主| 发表于 2013-11-10 15:32:11 | 显示全部楼层
oldbeginner 发表于 2013-11-10 11:14
现在要做的是简写FX1NProcessing函数,先看一下简写后的效果,复制到word上不到3页,字符数(含注释)约20 ...

FX1NProcessing里的内容很多,因为FX1NProcessing主要处理通讯协议,所以有大量的串口函数包含在里面,在理解的时候,并没有研究相关串口函数,而是直接认为其功能是可以实现的,这样能把精力放在协议上。鉴于协议已经理清了思路,现在回过头来确认这些串口函数。
对uart相关函数的理解放在笔记05中。
http://www.amobbs.com/thread-5558494-1-1.html

出0入0汤圆

发表于 2013-11-10 20:13:36 | 显示全部楼层
大牛啊

出0入0汤圆

 楼主| 发表于 2013-11-14 19:57:41 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-15 19:14 编辑
oldbeginner 发表于 2013-11-10 15:32
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 int  backdata=0;
    unsigned int  tempdata=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函数了。
   


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-11-15 15:42:00 | 显示全部楼层

再进入ReadCode函数中(原FX1NProcessing函数中并没有,没有可读性,把Read code功能另外写成了一个函数)


忽略不相干后的函数表达如下,
void ReadCode(void)

{

                  计算得到读取地址:

                  计算得到读取字节数:

                   计算 sumdata 并发送读取字节
                    
                  unsigned int  tempascdata=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码。




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2013-11-15 16:05:18 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-15 17:07 编辑
oldbeginner 发表于 2013-11-15 15:42
再进入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码。

出0入0汤圆

 楼主| 发表于 2013-11-15 17:00:04 | 显示全部楼层
本帖最后由 oldbeginner 于 2013-11-15 17:08 编辑
oldbeginner 发表于 2013-11-15 16:05
既然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

现在有点感觉了。

出0入0汤圆

发表于 2013-11-19 22:28:30 | 显示全部楼层
学习!!

出0入0汤圆

 楼主| 发表于 2013-11-25 09:35:35 | 显示全部楼层
oldbeginner 发表于 2013-11-15 17:00
最后一个函数
// 函数名称: ascto0F
//

在笔记10中理解了文件管理后,把FX1N通讯处理和uart功能合并在一起,做一下测试。测试工具使用串口通讯工具,而不是梯形图软件,因为目前开源的通讯协议还不完整。目前,只测试程序结构是好的,至于要和梯形图通讯的完整协议还要暂时缓一下。

采用了另外一个相关文件,作为测试核对表,http://wenku.baidu.com/link?url= ... 0w715KnYWp3WHgknOLe




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-10-15 09:45:42 | 显示全部楼层
学习了  太强大了
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 08:56

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表