EhterCAT_SOEM


前言

SOEM简单开放式ETherCAT主站,支持Linux,Windows双系统,这里讲解是SOEM 1.3.1版本基于Windows平台编译后的源码eepromtool.c,在SOEM-1.3.1\test\win32\eepromtool文件夹中。
源码可以到这里下载:百度网盘,提取码:5679


一、eepromtool.c的作用

就如其名字一样,作用就是实现对ESC SII的读写,这里主要就是讲解源码中读写函数应该怎么使用,但是大家应该知道EEPROM数据不能随便更改,很有可能更改后就不能与从站通讯了,需要重新烧写EEPROM,大家谨慎操作,本文章主要讲解对0-7字的读写。

二、读写EEPROM步骤

根据倍福官网 手册 描述操作EEPROM步骤如下:

  1. 检查 EEPROM 状态寄存器的 Busy 位是否清零(0x0502[15]=0)和 EEPROM接口不忙,否则等到 EEPROM 接口不忙。
  2. 检查 EEPROM 状态寄存器的错误位是否被清除。如果没有,请写入“000”到命令寄存器(寄存器 0x​​0502 位 [10:8])。
  3. 将 EEPROM 字地址写入 EEPROM 地址寄存器。
  4. 只写命令:将写入数据放入 EEPROM 数据寄存器(仅 1 个字/2 个字节)。
  5. 通过写入控制寄存器发出命令。
    a) 对于读取命令,将 001 写入命令寄存器 0x​​0502[10:8]。
    b) 对于写入命令,将 1 写入写入启用位 0x0502[0] 并将 010 写入命令寄存器0x0502[10:8]。两个位都必须写在一帧中。写使能位实现写保护机制。对同一帧下发的后续 EEPROM 命令有效并在之后自行清理。写使能位不需要从 PDI 写入,如果它控制EEPROM接口。
    c) 对于重载命令,将 100 写入命令寄存器 0x​​0502[10:8]。
  6. 如果 EtherCAT 帧没有错误,则在 EOF 之后执行该命令。通过 PDI 控制,命令立即执行。
  7. 等到 EEPROM 状态寄存器的 Busy 位被清除。
  8. 检查 EEPROM 状态寄存器的错误位。错误位通过清除清除命令寄存器。如果缺少 EEPROM 确认,则重试命令(返回步骤 5)。如果必要时,在重试之前等待一些时间,让慢速 EEPROM 在内部存储数据。
  9. a) 对于读取命令:读取数据在 EEPROM 数据寄存器中可用(2 或 4 个字,取决于 ESC 检查寄存器 0x​​0502[6])。
    b) 对于重载命令:ESC 配置被重载到适当的寄存器中。
    注意:命令寄存器位是自清零的。手动清除命令寄存器也将清除状态信息。

使用VS的命令行工具 x86 Nactive Tools Command Prompt for VC2019进入eepromtool文件夹之后,输入eepromtool可以看到如下提示:
在这里插入图片描述
使用方法: eepromtool 网卡ID 从站索引 操作 文件地址
如使用我的电脑读取1号站的EEPROM数保存为16进制数据到read.txt中:
eepromtool \Device\NPF_{36CBEDF6-B690-4C67-8C8B-9A2D1811234E} 1 ri read.txt
写入数据同理就不讲解了。使用eepromtool.exe直接读写EEPROM较简单就不说了,直接开始讲解源码。

1.读取EEPROM数据

eepromtool.c中关于读取的函数如下:
1.首先是主站夺回控制权,eeprom_read函数

/// <summary>
/// 夺回主站控制权
/// </summary>
/// <param name="slave">从站索引</param>
/// <param name="start">开始读取地址,这里是字地址,如从站别名字地址:4</param>
/// <param name="length">读取长度</param>
/// <returns>0:读取失败,1:读取成功</returns>
int eeprom_read(int slave, int start, int length)
{
   int i, wkc, ainc = 4;
   uint16 estat, aiadr;
   uint32 b4;
   uint64 b8;
   uint8 eepctl;
   //源码中限制了读取的大小MAXBUF=32768,也就是只读取32kb数据
   if((ec_slavecount >= slave) && (slave > 0) && ((start + length) <= MAXBUF))
   {
      aiadr = 1 - slave;
      eepctl = 2;//二进制10 
      //ECT_REG_EEPCFG地址0x0500,顺序寻址写0x500.1 == 1,强制清除0x501.0,主站从PDI夺回EEPROM控制权
      wkc = ec_APWR(aiadr, ECT_REG_EEPCFG, sizeof(eepctl), &eepctl , EC_TIMEOUTRET3); /* force Eeprom from PDI */
      eepctl = 0;
      //0x500.0 == 0,主站接管EEPROM控制权
      wkc = ec_APWR(aiadr, ECT_REG_EEPCFG, sizeof(eepctl), &eepctl , EC_TIMEOUTRET3); /* set Eeprom to master */

      estat = 0x0000;
      aiadr = 1 - slave;
      //ECT_REG_EEPSTAT地址0x0502,读取EEPRO状态
      wkc=ec_APRD(aiadr, ECT_REG_EEPSTAT, sizeof(estat), &estat, EC_TIMEOUTRET3); /* read eeprom status */
      estat = etohs(estat);
	  //EC_ESTAT_R64 0x0040 二进制 0100 0000,跟读取的estat进行与操作,及可得知EEPROM支持读取的字节数,ET1100和ET1200支持8字节,其他ESC芯片为4字节。
      if (estat & EC_ESTAT_R64)
      {
         ainc = 8;
         for (i = start ; i < (start + length) ; i+=ainc)
         {
         	//读取EEPROM数据
            b8 = ec_readeepromAP(aiadr, i >> 1 , EC_TIMEOUTEEP);
            ebuf[i] = (uint8) b8;
            ebuf[i+1] = (uint8) (b8 >> 8);
            ebuf[i+2] = (uint8) (b8 >> 16);
            ebuf[i+3] = (uint8) (b8 >> 24);
            ebuf[i+4] = (uint8) (b8 >> 32);
            ebuf[i+5] = (uint8) (b8 >> 40);
            ebuf[i+6] = (uint8) (b8 >> 48);
            ebuf[i+7] = (uint8) (b8 >> 56);
         }
      }
      else
      {
         for (i = start ; i < (start + length) ; i+=ainc)
         {
            b4 = (uint32)ec_readeepromAP(aiadr, i >> 1 , EC_TIMEOUTEEP);
            ebuf[i] = (uint8) b4;
            ebuf[i+1] = (uint8) (b4 >> 8);
            ebuf[i+2] = (uint8) (b4 >> 16);
            ebuf[i+3] = (uint8) (b4 >> 24);
         }
      }

      return 1;
   }

   return 0;
}

2.读取EEPROM数据,ec_readeepromAP函数

/** Read EEPROM from slave bypassing cache. APRD method.
 * @param[in] context     = context struct
 * @param[in] aiadr       = auto increment address of slave
 * @param[in] eeproma     = (WORD) Address in the EEPROM
 * @param[in] timeout     = Timeout in us.
 * @return EEPROM data 64bit or 32bit
 */
uint64 ecx_readeepromAP(ecx_contextt *context, uint16 aiadr, uint16 eeproma, int timeout)
{
   uint16 estat;
   uint32 edat32;
   uint64 edat64;
   ec_eepromt ed;
   int wkc, cnt, nackcnt = 0;

   edat64 = 0;
   edat32 = 0;
   //对应上述读写EEPRO步骤1,读取0x502,检查EEPROM是否繁忙
   if (ecx_eeprom_waitnotbusyAP(context, aiadr, &estat, timeout))
   {
   	  //EC_ESTAT_EMASK 0x7800 二进制0111 1000 0000 0000 对应步骤2检查错误位是否被清除
      if (estat & EC_ESTAT_EMASK) /* error bits are set */
      {
      	 //EC_ECMD_NOP 0x0000
         estat = htoes(EC_ECMD_NOP); /* clear error bits */
         //对应步骤2,清除错误位,写000到0x​​0502 位 [10:8]
         wkc = ecx_APWR(context->port, aiadr, ECT_REG_EEPCTL, sizeof(estat), &estat, EC_TIMEOUTRET3);
      }

      do
      {
      	 //EC_ECMD_READ 0x0100 二进制 0000 0001 0000 0000 将001写入命令寄存器 0x​​0502[10:8],读取EEPROM命令
         ed.comm = htoes(EC_ECMD_READ);
         //读取地址,字地址
         ed.addr = htoes(eeproma);
         //这里我还没搞懂是什么
         ed.d2   = 0x0000;
         cnt = 0;
         do
         {
         	//ECT_REG_EEPCTL 0x502 ,写入读取命令
            wkc = ecx_APWR(context->port, aiadr, ECT_REG_EEPCTL, sizeof(ed), &ed, EC_TIMEOUTRET);
         }
         while ((wkc <= 0) && (cnt++ < EC_DEFAULTRETRIES));
         if (wkc)
         {
         	//延时
            osal_usleep(EC_LOCALDELAY);
            estat = 0x0000;
            //读取0x502.15 等待EEPOM繁忙位清除,0x502.15 == 0;
            if (ecx_eeprom_waitnotbusyAP(context, aiadr, &estat, timeout))
            {         
            	//EC_ESTAT_NACK 0x2000 二进制 0010 0000 0000 0000检查EEPROM应答位0x502.13,是否应答丢失   	
               if (estat & EC_ESTAT_NACK)
               {
               	  //EEPROM无应答或命令无效
                  nackcnt++;
                  osal_usleep(EC_LOCALDELAY * 5);
               }
               else
               {
               	  //无错误
                  nackcnt = 0;
                  //EEPROM支持读取的字节数
                  if (estat & EC_ESTAT_R64)
                  {
                  	 //8字节
                     cnt = 0;
                     do
                     {
                     	//读取EEPROM数据
                        wkc = ecx_APRD(context->port, aiadr, ECT_REG_EEPDAT, sizeof(edat64), &edat64, EC_TIMEOUTRET);
                     }
                     while ((wkc <= 0) && (cnt++ < EC_DEFAULTRETRIES));
                  }
                  else
                  {
                     //4字节
                     cnt = 0;
                     do
                     {
                     	//读取EEPROM数据
                        wkc = ecx_APRD(context->port, aiadr, ECT_REG_EEPDAT, sizeof(edat32), &edat32, EC_TIMEOUTRET);
                     }
                     while ((wkc <= 0) && (cnt++ < EC_DEFAULTRETRIES));
                     edat64=(uint64)edat32;
                  }
               }
            }
         }
      }
      while ((nackcnt > 0) && (nackcnt < 3));
   }

   return edat64;
}

这里举两个例子,比如
1.使用eeprom_read函数读取从站1别名:
eeprom_read(1,8,2);

2.直接使用ec_readeepromAP函数读取从站1别名:
ec_readeepromAP(1,4, EC_TIMEOUTEEP);
但是需要注意的是直接使用ec_readeepromAP函数时,要先让主站夺回控制权。

2.写入EEPROM数据

查看源码中eeprom_write和ec_writeeepromAP函数就会发现,其作用与对应的eeprom_read和ec_readeepromAP一模一样。但是写入EEPROM步骤可不是直接写入那么简单。这里还是以写入从站别名为例。

2.1 写入从站别名

同样举两个例子,比如
1.使用eeprom_write函数读取从站1别名:
//赋值
ebuf[8] = 0x04;//写入别名0x04
eeprom_read(1,8,2);

2.直接使用ec_readeepromAP函数读取从站1别名:
//写入别名
ec_writeeepromAP(1, 4, 0x04, EC_TIMEOUTEEP);
同样需要注意的是直接使用ec_writeeepromAP函数时,要先让主站夺回控制权。

2.2 读取0-6字数据

读取0-6字数据,用于计算校验和

2.3 计算校验和

计算0-6字的校验和在写入EEPROM数据的步骤中最为关键,能不能写入成功最后就是看校验和是否正确,ESC在上电或是复位后会自动读取0-7字数据(ESC寄存器配置区)并装入相应的寄存器,并检查校验和,校验和如果不正确则写入不成功。
倍福官网介绍的校验和计算方式为:
Low byte contains remainder of division of word 0 to word 6 as unsigned number divided by the polynomial x8+x2+x+1(initial value0xFF).
NOTE: For debugging purposes it is possible to disable the checksum validation with a checksum value of 0x88A4. Never use this for production!
低字节包含字 0 到字 6 的除以无符号数除以多项式 x8+x2+x+1(初始值 0xFF)的余数。
注意:出于调试目的,可以使用校验和值 0x88A4 禁用校验和验证。切勿将其用于生产!
这里可以百度一下CRC8计算器,给大家推荐一个CRC8计算器
计算器设置:
在这里插入图片描述

或者使用代码

unsigned char crc_high_first(unsigned char* ptr, unsigned char len)
{
	unsigned char i;
	unsigned char crc = 0xff;/* 计算的初始crc值 */

	while (len--)
	{
		crc ^= *ptr++;  /* 每次先与需要计算的数据异或,计算完指向下一数据 */
		for (i = 8; i > 0; --i)   /* 下面这段计算过程与计算一个字节crc一样 */
		{
			if (crc & 0x80)
				crc = (crc << 1) ^ 0x07;//多项式
			else
				crc = (crc << 1);
		}
	}

	return (crc);
}

2.4 写入校验和

与2.2节一样,写入校验和到字地址7即可。


总结

有什么理解的不正确或是写错的地方,大家可以讨论,另外有不理解的也可以评论,再提供一个简单的读写EEPROM项目,提取码:5679

Logo

更多推荐