第4章 FMMU内存管理单元
4.1 FMMU简介
Fieldbus Memory Management Units(FMMU),通过内部的映射将逻辑地址转换为ESC内部实际的物理地址。我在使用逻辑寻址的时候需要用到FMMU,我们在数据包头填写的地址就是这一包数据逻辑地址的起始地址。逻辑寻址时,如果本报数据中有逻辑地址与FMMU所配置的逻辑地址匹配,那么就会对FMMU逻辑地址所对应的物理地址进行操作。
4.2 映射举例
FMMU的配置寄存器从0x0600开始,每个FMMU通道有16个字节的配置寄存器。现在将物理地址0x0F01:10x0F02:0映射至逻辑地址0x00001234:30x00001235:2,映射结果如下表所示。逻辑地址长度2是因为在映射的时候横跨了两个字节的长度。
FMMU配置寄存器 | 寄存器偏移 | 值 |
---|---|---|
逻辑地址起始地址 | 0x0:0x3 | 0x00001234 |
逻辑地址长度 | 0x4:0x5 | 2 |
逻辑地址起始位 | 0x6 | 3 |
逻辑地址终止位 | 0x7 | 2 |
物理地址起始地址 | 0x8:0x9 | 0x0F01 |
物理地址起始位 | 0xA | 1 |
类型 | 0xB | 写 |
激活 | 0xC | 1(使能) |
4.3 FMMU与SM的区别
- FMMU是用于地址的映射,用于数据的寻址,SM是用于内存访问的管理,保证数据的一致性与安全
- 一个FMMU可以配置为双向的(读或写或读写),SM只能配置为单向(读或写)。一般在过程数据传输的时候两个会配合使用,FMMU用来寻址,SM用来数据交换。
4.4 FMMU其他特性
- 同一个ESC中同方向(读或写)的两个FMMU,如果其中一个使用的是按位映射(逻辑映射的起始位不是0或者终止位不是7或者物理地址的起始位不是0),那么逻辑地址范围之间必须间隔至少3个逻辑字节,且间隔的逻辑地址没有被任何相同类型的FMMU配置。如上面举例的图中所示,逻辑地址映射的最后一个字节的地址是0x00001235,那么与其相邻的FMMU的逻辑起始地址最小是0x00001239。如果不是采用的位映射,那么逻辑地址可以相邻。
- 位映射一般只用于数字输出寄存器(0x0F00:0x0F03),其他寄存器和内存总是按照字节来映射。
- 每个逻辑地址最多能只能由一个FMMU读加一个FMMU写或者一个FMMU读写。如果同一个逻辑地址被两个或这两个以上的FMMU配置,且方向相同,则使用编号较低的FMMU,其他的FMMU被忽略。
- 一个物理地址可以被多个FMMU配置,它们的访问不冲突。
- 对于同一个逻辑地址,使用一个读加一个写FMMU的效果与使用一个读写FMMU的效果是一样的。
- 配置为读写的FMMU不可以和SM共同使用。
- 任何地址都支持按位读取,例如一个字节的逻辑地址,在ESC1中映射了bit0bit2,在ESC2中映射了bit3bit7,那么在读取ESC2的数据的时候,只会改变bit3bit7,bit0bit2不会被改变。
4.5 FMMU配置实现
- 配置Ethercat地址空间的逻辑地址、逻辑地址长度、逻辑起始位、终止位。
- 配置物理地址的起始地址、物理起始位。
- 配置FMMU的传输方向,激活FMMU。
FMMU常用于COE中的PDO通信,FMMU与SM配合使用,FMMU映射的内存就是SM缓存模式所管理的内存。所以获取FMMU的配置参数取通常取决于SM的配置参数。
4.5.1 代码示例
/**
* @******************************************************************************:
* @func: [mecm_fmmu_config]
* @description: SM 配置,配置信息应该事先由EEPROM中读取出来或者手动填充
* @note:
* @author: gxf
* @param [uint16_t] slave 从站编号1,2,3,4,5...
* @param [uint8_t] sm_cnt SM编号,0,1,2,3...
* @return [*] ret<=0:配置失败
* @==============================================================================:
*/
int mecm_fmmu_config(uint16_t slave,uint8_t fmmu_cnt)
{
int ret = 0;
uint8_t buf[13];
uint16_t i = 0;
uint16_t tmp = 0;
uint16_t configaddr = mecm_slave[slave].configaddr;
/* 获取逻辑地址起始地址 */
tmp = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_logi_start_addr;
/* 大小端转换,然后将配置保存在临时数组中,之后一起配置 */
buf[0] = htoel(tmp)&0xFF;
buf[1] = (htoel(tmp)>>8)&0xFF;
buf[2] = (htoel(tmp)>>16)&0xFF;
buf[3] = (htoel(tmp)>>24)&0xFF;
/* 获取逻辑地址映射长度 */
tmp = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_length;
/* 大小端转换,然后将配置保存在临时数组中,之后一起配置 */
buf[4] = htoes(tmp)&0xFF;
buf[5] = (htoes(tmp)>>8)&0xFF;
buf[6] = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_logo_start_bit;
buf[7] = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_logo_stop_bit;
tmp = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_phys_start_addr;
/* 大小端转换,然后将配置保存在临时数组中,之后一起配置 */
buf[8] = htoes(tmp)&0xFF;
buf[9] = (htoes(tmp)>>8)&0xFF;
buf[10] = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_phys_start_bit;
buf[11] = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_type;
buf[12] = mecm_slave[slave].rFMMU[fmmu_cnt].fmmu_activate;
/* 配置FMMU */
ret = mecm_FPWR(configaddr,ESC_SM0_REG_STRART_ADDR+sm_cnt*8,buf,13,50000);
return ret;
}