目录
- 简介
- 环境
- 硬件接线
- MCU一侧的初始化
- 时钟
- FSMC
- 外部中断
- timer
- 协议栈生成
- EtherCAT Slave
- Slave infomation
- generic
- hardware
- EtherCAT State Machine
- Synchronisation
- Applicaiton
- ProcessData
- Mailbox
- OD TOOL
- 协议栈移植
- 协议栈集成和错误初步解决
- 启动协议栈
- 应用开发
- 集成到TWINCAT
- 集成
- 烧录ESC 配置
- 测试运行
简介
EtherCAT是一种快速准时的工业以太网协议,多用于运动控制和远程IO
EtherCAT协议分为Master和Slave,Slave的协议栈物理层芯片成为ESC
AX58100就是一个ESC,内部包含9KB的双端内存。
EtherCAT Master 对ESC更新数据的时候,是硬件自动完成对9KB DPRAM的操作,无需MCU参与。MCU自己决定什么时候处理数据。
环境
SSC:5.12, 5.1.3的软件版本在生成工程的时候有patch fail的错误
KEIL:5.3.6
CUBEMX:6.11.1
硬件接线
接口 | 作用 |
---|---|
FSMC | 访问AX58100的RAM |
PDI INT | 中断信号,向MCU指示有Process Data更新 |
SYNC0 INT | 中断信号,向MCU指示SYNC0时间到达,未使用 |
SYNC1 INT | 中断信号,向MCU指示SYNC1时间到达,未使用 |
详细硬件接线如下
PIN | 功能 | PIN | 功能 | PIN | 功能 |
---|---|---|---|---|---|
PF0 | FSMC_A0 | PD14 | FSMC_D0 | PG12 | FSMC_NE4 |
PF1 | FSMC_A1 | PD15 | FSMC_D1 | PD4 | FSMC_NOE |
PF2 | FSMC_A2 | PD0 | FSMC_D2 | PD5 | FSMC_NWE |
PF3 | FSMC_A3 | PD1 | FSMC_D3 | ||
PF4 | FSMC_A4 | PE7 | FSMC_D4 | ||
PF5 | FSMC_A5 | PE8 | FSMC_D5 | ||
PF12 | FSMC_A6 | PE9 | FSMC_D6 | PC3 | SYNC0_INT |
PF13 | FSMC_A7 | PE10 | FSMC_D7 | PC1 | SYNC1_INT |
PF14 | FSMC_A8 | PE11 | FSMC_D8 | PC0 | PDI_INT |
PF15 | FSMC_A9 | PE12 | FSMC_D9 | ||
PG0 | FSMC_A10 | PE13 | FSMC_D10 | ||
PG1 | FSMC_A11 | PE14 | FSMC_D11 | ||
PG2 | FSMC_A12 | PE15 | FSMC_D12 | ||
PD8 | FSMC_D13 | ||||
PD9 | FSMC_D14 | ||||
PD10 | FSMC_D15 |
MCU一侧的初始化
时钟
时钟初始化,拉到最高
FSMC
FSMC挂载在AHB3片内总线上,拿到的频率应该就是图上的AHP总线频率168Mhz,一个tick是6ns左右。
data setup time :地址建立时间为0,设置为1个tick
Address setup time :数据建立时间参考下图,最差情况下为566ns,大约为95个tick
Bus turn arround time :读写切换时间为5ns,设置为2个tick
以上参数参考AX58100的数据手册,我自己的理解可能有误,请自行斟酌。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
CubeMX中FSMC初始化参数如下
小测试
FSMC配置完毕之后,AX58100的双端RAM就挂载到0x6C000000上了,我们可以尝试对这一段数据做读取操作,将前8KB数据打印出来。
static uint8_t AX58100_RAM[8192] = {0};
SEGGER_RTT_printf(0, "*** START ***\n");
memcpy(AX58100_RAM,(uint8_t*)(0x6C000000 + 0), 8192);
for(uint8_t i = 0; i < 128; i++)
{for(uint8_t j = 0; j < 64; j++){SEGGER_RTT_printf(0, "%02x ", AX58100_RAM[i*128 + j]);}SEGGER_RTT_printf(0, "\r\n");
}
HAL_Delay(500);
注意:如果没有对AX58100做配置的话,此处不一定能读取出来如下图中的数据。AX58100的配置与EtherCAT描述文件相关,我在后面的章节描述的。这个测试可以在烧录描述文件之后再做。
但是,上面的代码至少不应该触发硬件中断,如果触发了硬件中断,有可能是FSMC片选线等配置问题。
外部中断
引脚正常为高电平,有信号的时候为低电平
重写HAL_GPIO_EXTI_Callback,直接短接中断引脚到地,测试能否进入中断函数中去
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if(GPIO_Pin==PDI_INT_Pin){//}else if(GPIO_Pin==SYNC0_INT_Pin){//}else if(GPIO_Pin==SYNC1_INT_Pin){//}
}
timer
EtherCAT需要一个1ms timer定时喂狗,MCU一侧需要准备一个1mstimer和相应的回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim == &htim3){//}
}
做完上述之后,在main.h中添加外设和部分C库驱动,我们后面会直接把main.h引入到EtherCAT协议栈中
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "tim.h" /* timer */
#include "SEGGER_RTT.h" /* print */
#include "string.h" /* memcpy */
#include "stdlib.h" /* malloc */
/* USER CODE END Includes */
协议栈生成
上面我们准备好了协议栈底层对接的硬件,现在我们开始准备协议栈本体。
协议栈使用SSC工具生成
打开软件,FILE -> new,选择default默认模板。
选择模板之后,整体界面如下
EtherCAT Slave
协议栈文件集合,保持不变
Slave infomation
从站设备名称,图片,版本号等信息,保持不变
generic
SYSTEM_HEADER_FILE:设置为#include “main.h”,注意把所有MCU外设相关的头文件引入到main.h中
hardware
EL_9800HW:使用倍福官方的EL9800开发板,设置为0不使用
MCI_HW:使用并口,设置为1使用
CONTROLLER_32bit:STM32是32位处理器,设置为1
CONTROLLER_16bit:STM32是16位处理器,设置为0
_PIC18,_PIC24:STM32不是这两个处理器,设置为0
UC_SET_ECAT_LED:由单片机设置EtherCAT状态灯,这里先不处理,设置为0
其余配置项保持默认
EtherCAT State Machine
保持不变
Synchronisation
ECAT_TIMER_INT:用于看门狗的喂狗定时器,这里设置为1开启
Applicaiton
全部设置为0
ProcessData
保持不变
Mailbox
保持不变,后面按需求开启
关于Mailbox和ProcessData地址的描述
AX58100 RAM分配如下:
0x0000~0x0FFF (ESC Memory Map,EtherCAT协议栈配置相关,4KB)
0x1000~0x2FFF (Process Data RAM,过程数据,4KB)SSC中的配置如下:
MBX_Write (MIN-MAX), 0x1000_0x2FFF, 实际使用0x1000~0x1080,128字节
MBX_Write (MIN-MAX), 0x1000_0x2FFF, 实际使用0x1080~0x1100,128字节
PD_Write (MIN-MAX), 0x1000_0x2FFF, 实际使用0x1100~0x1144,68字节
PD_Read (MIN-MAX), 0x1000_0x2FFF, 实际使用0x1400~0x1444,68字节
可以看出,SSC中的配置在58100的范围之内,能满足我们的要求
注意::
开启MCI_HW后出现如下警告,不用管,这个指示提醒你要完成mcihw.h中有关timer的宏定义,后面我们回去做。
编辑完毕之后记得保存SSC工程。所有配置如下
OD TOOL
OD TOOL实际上是一个Excel表格,用于编辑过程数据影像,COE Record,枚举等。SSC TOOL将会利用该表格生成ESI描述文件。
之后会弹出一个excel界面
这里我们只做输入输出数据的修改,配置数据、Module Slot、Diagnosis、ENUM都是能在此处编辑的,请参考《Application Note ET9300》文档。后面我们也会对其余配置做博客。
编辑输入输出数据如下
编辑完毕,保存并关闭EXCEL文件,点击创建新的从站文件
这里可以选择生成原码SRC和ESI文件的位置。
点击START开始
OK,到此为止协议栈文件生成成功了。后面就是移植的事情
对于SSC 5.1.3,这里会有一个patch fail,无伤大雅。
协议栈移植
协议栈集成和错误初步解决
keil中创建一个新的文件组,把协议栈文件全部加入keil环境中
直接编译,有一堆警告和问题。
我们主要要对接好mcihw.c 和mcihw.h两个文件,对接好之后这些警告和错误就消失了
打开mcihw.h
38行注释掉#include <malloc.h>,使用main.h中的stdlib.h申请释放内存
//#include <malloc.h>
48~68行,可以看到一些timer的宏定义,主要用于看门狗喂狗。对接timer函数如下:
#ifndef HW_GetTimer
/*** \todo Define a macro or implement a function to get the hardware timer value.<br>See SSC Application Note for further details, http://www.beckhoff.com/english.asp?download/ethercat_development_products.htm?id=71003127100387*/
#define HW_GetTimer() __HAL_TIM_GET_COUNTER(&htim3)
#endif#ifndef HW_ClearTimer
/*** \todo Define a macro or implement a function to clear the hardware timer value.<br>See SSC Application Note for further details, http://www.beckhoff.com/english.asp?download/ethercat_development_products.htm?id=71003127100387*/
#define HW_ClearTimer() __HAL_TIM_SET_COUNTER(&htim3, 0)
#endif/*** \todo Define the hardware timer ticks per millisecond.*/
#define ECAT_TIMER_INC_P_MS 1000
80行注释掉警告
//#warning "define access to timer register(counter)"
文件末尾增加中断和定时器的控制宏
#define INIT_ESC_INT
#define DISABLE_ECAT_TIMER_INT NVIC_DisableIRQ(TIM3_IRQn)
#define ENABLE_ECAT_TIMER_INT NVIC_EnableIRQ(TIM3_IRQn)
#define DISABLE_SYNC0_INT NVIC_DisableIRQ(EXTI3_IRQn)
#define ENABLE_SYNC0_INT NVIC_EnableIRQ(EXTI3_IRQn)
#define DISABLE_SYNC1_INT NVIC_DisableIRQ(EXTI1_IRQn)
#define ENABLE_SYNC1_INT NVIC_EnableIRQ(EXTI1_IRQn)
#define DISABLE_ESC_INT() NVIC_DisableIRQ(EXTI0_IRQn)
#define ENABLE_ESC_INT() NVIC_EnableIRQ(EXTI0_IRQn)
打开mcihw.c
118行,直接对pEsc赋值为0x6C000000,这是STM32 使用FSMC NE4的逻辑地址空间,此后pEsc就指向外挂双端内存的起始位置,长度为9KB。
pEsc = (ESC_MEM_ADDR ESCMEM *)(0x6C000000);
121-125行,注释掉警告
//#if _WIN32
//#pragma message ("The expected PDI Control value depends on the configured PDI (see ESC datasheet, register 0x140)")
//#else
// #warning "The expected PDI Control value depends on the configured PDI (see ESC datasheet, register 0x140)"
//#endif
127~135行,这个地方在循环读取u16PdiCtrl等待ESC准备好,ESC准备好之后才能退出循环。对于SSC 5.1.3工具生成的代码会在这个地方卡死,需要手动修改。我们使用的5.1.2不会
{UINT16 u16PdiCtrl = 0;do{HW_EscReadWord(u16PdiCtrl,ESC_PDI_CONTROL_OFFSET);u16PdiCtrl = SWAPWORD(u16PdiCtrl);} while (((u16PdiCtrl & 0xFF) < 0x8) || ((u16PdiCtrl & 0xFF) > 0xD) ); //5.1.2此处可以通过, 5.1.3建议调试到此处按值修改}
149行,HW_Init末尾追加timer3启动函数
/* enable the ESC-interrupt microcontroller specific,the macro ENABLE_ESC_INT should be defined in ecat_def.h */ENABLE_ESC_INT();HAL_TIM_Base_Start_IT(&htim3);return 0;
}
160-180行,对接PDI和看门狗定时器的终端服务函数,全部注释之后后面追加我们自己的定时中断函数
///
///**
// \brief Interrupt service routine for the interrupts from the EtherCAT Slave Controller
//*//#ifndef _WIN32
//interrupt
//#endif
//void HW_EcatIsr(void)
//{
// PDI_Isr();
//}///
///**
// \brief Timer interrupt service routine which will shall be called every ms
//*///* header of the ISR shall be defined in mcihw.h */
//TIMER_INT_HEADER
//void APPL_1MsTimerIsr(void)
//{
// ECAT_CheckTimer();
//}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if(GPIO_Pin==PDI_INT_Pin){PDI_Isr();}else if(GPIO_Pin==SYNC0_INT_Pin){Sync0_Isr();}else if(GPIO_Pin==SYNC1_INT_Pin){Sync1_Isr();}
}void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim == &htim3){ECAT_CheckTimer();}
}
启动协议栈
到此我们的移植对接工作接近末尾了,接下来我们准备启动EtherCAT协议栈
参考SSC-Device.c中未启用的main函数,EtherCAT的启动主要有如下步骤
#include "ecat_def.h"
#include "applInterface.h"
#include "SSC-Device.h"HW_Init();
MainInit();
bRunApplication = TRUE;
do
{MainLoop();} while (bRunApplication == TRUE);HW_Release();
我们把这些代码放到cubemx生成的main中也好,或者在RTOS中创建一个线程运行也好,都可以。这里我放main中
头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ecat_def.h"
#include "applInterface.h"
#include "SSC-Device.h"
/* USER CODE END Includes */
初始化和循环
/* USER CODE BEGIN 2 */HW_Init();MainInit();bRunApplication = TRUE;do{MainLoop();} while (bRunApplication == TRUE);HW_Release();/* USER CODE END 2 */
到此为止,我们的移植就做完了
编译之后的警告只剩余一些提示性的,屏蔽掉即可
//不用关注
../../Src/coeappl.c(883): warning: comparison of array 'ApplicationObjDic' not equal to a null pointer is always true [-Wtautological-pointer-compare]if(ApplicationObjDic != NULL)^~~~~~~~~~~~~~~~~ ~~~~//不用关注
../../Src/objdef.c(540): warning: result of comparison of constant 65535 with expression of type 'const unsigned char' is always true [-Wtautological-constant-out-of-range-compare]&&( pSubDesc[0] != 0xFF && pSubDesc[0] != 0xFE && pSubDesc[0] != 0xFFFF))~~~~~~~~~~~ ^ ~~~~~~//以下三个警告提示用户需要做相应的应用开发,我们完成这些应用函数的时候将其屏蔽即可
../../Src/SSC-Device.c(275): warning: "Implement input (Slave -> Master) mapping" [-W#warnings]#warning "Implement input (Slave -> Master) mapping"^
../../Src/SSC-Device.c(291): warning: "Implement output (Master -> Slave) mapping" [-W#warnings]#warning "Implement output (Master -> Slave) mapping"^
../../Src/SSC-Device.c(305): warning: "Implement the slave application" [-W#warnings]#warning "Implement the slave application"^
3 warnings generated.
应用开发
SSC-Device.c中
void APPL_InputMapping(UINT16* pData) //从MCU本地copy数据到ESC中
void APPL_OutputMapping(UINT16* pData) //从ESC中copy数据到MCU本地
void APPL_Application(void) //更新MCU本地的输入输出数据
我们使用按键和LED做示例
这里只是测试把KEY赋了固定值,然后按照LED的输出数据修改相关引脚电平
void APPL_InputMapping(UINT16* pData)
{uint8_t *pTmpData = (uint8_t *)pData;for (uint8_t j = 0; j < sTxPDOassign.u16SubIndex0; j++){switch (sTxPDOassign.aEntries[j]){/* TxPDO 1 */case 0x1A00://copy after subindex(2byte)memcpy(pTmpData, (uint8_t *)(&INPUT_GROUP0x6000) + 2, sizeof(INPUT_GROUP0x6000)-2);break;}}
}void APPL_OutputMapping(UINT16* pData)
{uint8_t *pTmpData = (uint8_t *)pData;for (uint8_t j = 0; j < sRxPDOassign.u16SubIndex0; j++){switch (sRxPDOassign.aEntries[j]){/* TxPDO 1 */case 0x1600://copy after subindex(2byte)memcpy((uint8_t *)(&OUTPUT_GROUP0x7000) + 2, pTmpData, sizeof(OUTPUT_GROUP0x7000)-2);break;}}
}void APPL_Application(void)
{INPUT_GROUP0x6000.KEY1 = 1;INPUT_GROUP0x6000.KEY2 = 0;INPUT_GROUP0x6000.KEY3 = 1;INPUT_GROUP0x6000.KEY4 = 0;INPUT_GROUP0x6000.KEY5 = 1;if(OUTPUT_GROUP0x7000.LED1 == 0)HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);elseHAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
}
集成到TWINCAT
集成
关闭TwinCAT
将生成的描述文件导入到TWINCAT路径下,C:\TwinCAT\3.1\Config\Io\EtherCAT
启动TwinCAT
没装驱动的话在这里安装驱动
点击扫描设备
第一次可能出现如下窗口,点击YES
出现如下现象,这是因为我们还没有将XML中的配置烧录到ESC中。
烧录ESC 配置
ESC使用EEPROM保存配置数据,配置项比如是否启用并口,是否启用SPI等等。
这个配置数据我们要写入到ESI文件中,通过Twincat烧录进入AX58100
配置的描述在AX58100 DataSheet 3.2章节中描述
AX58100 ESI Design Note中也提供了一些配置模板,如下
我们使用其中的AX58100 Local Bus Demo Board,值:080000000a000000000000000000
找文本工具打开描述文件SSC-Device.xml
修改如下内容
保存,替换掉Twincat中的描述文件
重载TwinCAT描述
删除EtherCAT Master(重要),重新Scan,按照下图指示更新EEPROM固件
设备重新上电,重新扫描设备,状态机进入OP模式
测试运行
KEY显示是符合代码的,开发人员要按照项目需求修改此处的应用程序。
LED写入也OK