0 工具准备
1.SOEM-master-1.4.0源码
1 ecx_siistring函数总览
/** Get string from SII string section in slave EEPROM.从SII的字符串段内获取字符串
* @param[in] context = context struct 句柄
* @param[out] str = requested string, 0x00 if not found 请求的字符串信息,0x00表示没找到
* @param[in] slave = slave number 从站序号
* @param[in] Sn = string number 字符串序号
*/
void ecx_siistring(ecx_contextt *context, char *str, uint16 slave, uint16 Sn)
{
uint16 a,i,j,l,n,ba;
char *ptr;
uint8 eectl = context->slavelist[slave].eep_pdi;
ptr = str;
/* find string section 寻找字符串段 */
/* 字符段格式是:1Byte的字符串长度+字符串 */
a = ecx_siifind (context, slave, ECT_SII_STRING);
if (a > 0)
{
ba = a + 2; /* skip SII section header 跳过SII段头 */
n = ecx_siigetbyte(context, slave, ba++); /* read number of strings in section 读取SII段字符串数量 */
if (Sn <= n) /* is req string available? 如果目标字符串序号小于实际的字符串数量 */
{
for (i = 1; i <= Sn; i++) /* walk through strings 逐个读字符串以获取目标字符串 */
{
l = ecx_siigetbyte(context, slave, ba++); /* length of this string 获取该字符串长度 */
if (i < Sn)
{
ba += l;
}
else
{
ptr = str;
for (j = 1; j <= l; j++) /* copy one string 拷贝字符串 */
{
if(j <= EC_MAXNAME)
{
*ptr = (char)ecx_siigetbyte(context, slave, ba++);
ptr++;
}
else
{
ba++;
}
}
}
}
*ptr = 0; /* add zero terminator 添加结束符 */
}
else
{
ptr = str;
*ptr = 0; /* empty string 空字符串 */
}
}
if (eectl)
{
/* if eeprom control was previously pdi then restore 如果先前EEPROM访问控制权为PDI,则还原回去 */
ecx_eeprom2pdi(context, slave);
}
}
可以看到,ecx_siistring函数的工作分为2部分:
(1)获取字符串分类段起始地址
(2)拷贝指定序号的字符串
1.1 获取字符串分类段起始地址
该部分的工作由ecx_siifind完成,该函数如下:
/** Find SII section header in slave EEPROM.在从站eeprom中寻找节头
* @param[in] context = context struct 句柄
* @param[in] slave = slave number 从站序号
* @param[in] cat = section category SII类别段的段头名
* @return byte address of section at section length entry, if not available then 0 SII类别段的长度地址,如果未找到,返回0
*/
int16 ecx_siifind(ecx_contextt *context, uint16 slave, uint16 cat)
{
int16 a;
uint16 p;
uint8 eectl = context->slavelist[slave].eep_pdi;
/* 将字地址x2得到字节地址 */
a = ECT_SII_START << 1;
/* read first SII section category 读取段头地址值 */
p = ecx_siigetbyte(context, slave, a++);
p += (ecx_siigetbyte(context, slave, a++) << 8);
/* traverse SII while category is not found and not EOF 在没找到SII类别段段头且不是EOF(0xffff,SII结束)时遍历SII*/
while ((p != cat) && (p != 0xffff))
{
/* SII类别段规则前4Byte: 段名 0x00 字长度低位 字长度高位 */
/* read section length 读取SII类别段的长度 */
p = ecx_siigetbyte(context, slave, a++);
p += (ecx_siigetbyte(context, slave, a++) << 8);
/* locate next section category 定位到下一个SII类别段 */
a += p << 1;
/* read section category 读取段头名 */
p = ecx_siigetbyte(context, slave, a++);
p += (ecx_siigetbyte(context, slave, a++) << 8);
}
if (p != cat)
{
a = 0;
}
if (eectl)
{
/* if eeprom control was previously pdi then restore 如果EEPROM访问控制权之前是PDI,则还原回去 */
ecx_eeprom2pdi(context, slave);
}
return a;
}
这个函数的主要功能就是在ECT_SII_START(分类附加信息,字地址0x40)开始位置查找相应的关键字(函数中使用的是字节地址,因此将ECT_SII_START左移一位x2得到字节地址),确定需要查找的分类段在EEPROM的起始地址。关于分类段的关键字定义如下:
从上表可以看到,我们需要寻找的是STRINGS,也就是10。因此在ecx_siistring函数最前面有如下语句:
a = ecx_siifind (context, slave, ECT_SII_STRING);
ECT_SII_STRING的值为10,也就是去寻找字符串段段头地址。
值得说明的是,查找分类段时需要根据段的长度跳转到下一个段头进行匹配,而不是逐个对比EEPROM数据,这样可以避免一些和关键字重复的EEPROM数据的干扰。SII的段头定义如下:
注:长度是以字为单位的。
1.2 拷贝指定序号的字符串
在获取了字符串分类段的段头地址后,接下来只需要按照下表定义的EEPROM字符串段信息结构获取字符串即可:
相关语句如下:
if (Sn <= n) /* is req string available? 如果目标字符串序号小于实际的字符串数量 */
{
for (i = 1; i <= Sn; i++) /* walk through strings 逐个读字符串以获取目标字符串 */
{
l = ecx_siigetbyte(context, slave, ba++); /* length of this string 获取该字符串长度 */
if (i < Sn)
{
ba += l;
}
else
{
ptr = str;
for (j = 1; j <= l; j++) /* copy one string 拷贝字符串 */
{
if(j <= EC_MAXNAME)
{
*ptr = (char)ecx_siigetbyte(context, slave, ba++);
ptr++;
}
else
{
ba++;
}
}
}
}
*ptr = 0; /* add zero terminator 添加结束符 */
}
else
{
ptr = str;
*ptr = 0; /* empty string 空字符串 */
}
上述语句的主要操作就是读取指定顺序的字符串内容,在SOEM主站中,只去读取顺序为1的字符串信息,该字符串便是从站名。这里有个地方需要特别注意,拷贝字符串时不要写越界,这里SOEM主站已经加上了防止越界写内存的if语句。此外,字符串末尾应添加0x00表示结束。
2 总结
SOEM主站在初始化阶段,会从SII读取包括从站名在内的一些字符串信息。该工作通过ecx_siistring函数完成,它主要的工作可以概括如下:
(1)获取字符串分类段起始地址
(2)拷贝指定序号的字符串