STM32与LAN9252构建EtherCAT从站(五):STM32与LAN9252适配

  1. 一、 STM32与LAN9252构建EtherCAT从站(一):项目简介
  2. 二、 STM32与LAN9252构建EtherCAT从站(二):使用SSC生成EtherCAT协议栈和XML文件
  3. 三、 STM32与LAN9252构建EtherCAT从站(三):LAN9252的XML文件
  4. 四、 STM32与LAN9252构建EtherCAT从站(四):STM32配置SPI
  5. 五、 STM32与LAN9252构建EtherCAT从站(五):STM32与LAN9252适配
  6. 六、 STM32与LAN9252构建EtherCAT从站(六):TwinCAT2的使用和从站测试

STM32与LAN9252构建EtherCAT从站(五):STM32与LAN9252适配

这一章讲解STM32与LAN9252的全面适配。

1.硬件

既然是全面适配,那不得不略微提一下硬件连接,以下是相关区域的原理图:
lan1
lan2
可以看到,LAN9252与STM32通信一共用了7根线,4根SPI,3根外部中断,这跟我们上文STM32与LAN9252构建EtherCAT从站(四):STM32配置SPI讲解的内容是一致的。

2.软件

软件的适配主要包括以下几点:

  1. 协议栈移植,至少做到.c文件没有报错。上一章我们介绍了方法,后续的步骤留给各位自行完成,千万不要拿来主义。
  2. SPI驱动移植,上一章我们已经完成。
  3. 对接接口。将协议栈暴露给我们的几个函数,在代码相应的位置进行调用。
  4. 编写业务逻辑。根据第二章在Excel中设计的IO交互数据,在指定的函数中编写业务逻辑。

2.1对接接口

整个EtherCAT协议栈期望的我们的程序主体框架的伪代码如下:

void    APPL_AckErrorInd(UINT16 stateTrans) {
}
UINT16 APPL_StartMailboxHandler(void) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_StopMailboxHandler(void) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_StartInputHandler(UINT16 *pIntMask) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_StopInputHandler(void) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_StartOutputHandler(void) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_StopOutputHandler(void) {
    return ALSTATUSCODE_NOERROR;
}
UINT16 APPL_GenerateMapping(UINT16 *pInputSize, UINT16 *pOutputSize){
    return ALSTATUSCODE_NOERROR;
}
void APPL_InputMapping(UINT16 *pData) {
}
void APPL_OutputMapping(UINT16 *pData) {
}
void APPL_Application(void) {
}

void EtherCatIRQ(){
    PDI_Isr();
}

void Sync0IRQ(){
    Sync0_Isr();
}

void Sync0IRQ(){
    Sync1_Isr();
}

void Timer1msIRQ(){
    ECAT_CheckTimer();
}

int main(){
    HW_Init();
    MainInit();
    while(1){
        MainLoop();
    }
}

其中,这些函数均由协议栈提供:

  • PDI_Isr():EtherCAT主中断响应,需要配置到外部中断中,跟LAN9252的44脚对应,下降沿触发。
  • Sync0_Isr():分布式时钟同步信号0,需要配置到外部中断中,跟LAN9252的18脚对应,下降沿触发。
  • Sync1_Isr():分布式时钟同步信号1,需要配置到外部中断中,跟LAN9252的34脚对应,下降沿触发。
  • ECAT_CheckTimer():1ms定时器溢出中断,我这边配置在STM32的TIM8的break interrupt上。
  • HW_Init():硬件初始化函数,MCU通过此函数感应LAN9252设备的存在与否。
  • MainInit():协议栈初始化函数。
  • MainLoop():协议栈主循环,主while(1)中尽量只放此函数,其他业务逻辑在APPL_Application(void)函数中编写,后面介绍。

这些函数需要根据自己的业务逻辑编写:

  • APPL_InputMapping(UINT16 *pData) 输入数据映射
  • APPL_OutputMapping(UINT16 *pData) 输出数据映射
  • APPL_Application(void) 主业务逻辑

这些函数保持默认即可,也可添加自己的业务逻辑:

  • APPL_AckErrorInd(UINT16 stateTrans) EtherCAT通信故障时回调
  • APPL_StartMailboxHandler(void) 邮箱消息接收前回调
  • APPL_StopMailboxHandler(void) 邮箱消息接收后回调
  • APPL_StartInputHandler(UINT16 *pIntMask) 输入映射前回调
  • APPL_StopInputHandler(void) 输入映射后回调
  • APPL_StartOutputHandler(void)输出映射前回调
  • APPL_StopOutputHandler(void) 输出映射后回调

最后一个APPL_GenerateMapping()函数有固定的模板,我们后面介绍。

2.2 中断接口

2.2.1 PDI_Isr()

这个函数来自于ecatappl.c文件,头文件定义取自ecatappl.h,我们在主程序中直接#include applInterface.h即可。

IRQ
将此函数配置到外部中断服务函数中去,我这边使用的是EXTI0_IRQHandler,并且根据习惯,我将这个服务函数从stm32f1xx_it.c中移动到main.c中去了,仅做参考。
EXT0
需要再次提醒的是,上一章已经说过,CubeMX生成的中断初始化函数,在gpio.c中,有以下代码:
gpio
正如我所说,设备上电后我们不可以直接调用HAL_NVIC_EnableIRQ函数将各种中断使能,因为此时协议栈相关数据都没初始化好,直接使能这些中断将导致严重的逻辑问题。因此上述代码后面的使能语句,都需要注释掉。

2.2.2 Sync0_Isr() 和 Sync1_Isr()

这个函数来自于ecatappl.c文件,头文件定义取自ecatappl.h,我们在主程序中直接#include applInterface.h即可。
我们将这个函数配置到EXTI9_5_IRQHandlerEXTI4_IRQHandler中:

void EXTI9_5_IRQHandler(void) {
    /* USER CODE BEGIN EXTI9_5_IRQn 0 */
    DISABLE_ESC_INT();
    Sync0_Isr();
    ENABLE_ESC_INT();
    /* USER CODE END EXTI9_5_IRQn 0 */
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
    /* USER CODE BEGIN EXTI9_5_IRQn 1 */

    /* USER CODE END EXTI9_5_IRQn 1 */
}

void EXTI4_IRQHandler(void) {
    /* USER CODE BEGIN EXTI4_IRQn 0 */
    DISABLE_ESC_INT();
    Sync1_Isr();
    ENABLE_ESC_INT();
    /* USER CODE END EXTI4_IRQn 0 */
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
    /* USER CODE BEGIN EXTI4_IRQn 1 */

    /* USER CODE END EXTI4_IRQn 1 */
}
2.2.3 ECAT_CheckTimer()

这个函数来自于ecatappl.c文件,头文件定义取自ecatappl.h,我们在主程序中直接#include applInterface.h即可。
我们将其配置到TIM8的break interrupt服务函数中去:

void TIM8_BRK_IRQHandler(void) {
    /* USER CODE BEGIN TIM8_BRK_IRQn 0 */
    ECAT_CheckTimer();
    /* USER CODE END TIM8_BRK_IRQn 0 */
    HAL_TIM_IRQHandler(&htim8);
    /* USER CODE BEGIN TIM8_BRK_IRQn 1 */

    /* USER CODE END TIM8_BRK_IRQn 1 */
}

2.3 主程序接口

2.3.1 HW_Init()

这个函数来自9252_HW.c, 在9252HW.h中有其定义,主程序中直接#include ecatslv.h即可。

2.3.2 HW_Init()

这个函数来自ecatappl.c, 在applInterface.h中有其定义,主程序中直接#include applInterface.h即可。

2.3.2 MainLoop()

同上。

2.4 业务逻辑接口

上面提到很多回调函数,在EtherCAT协议栈中,使用了extern关键字来调用那些函数,因此我们需要有这些函数的实体。

2.4.1 APPL_GenerateMapping()

这个函数是根据我们定义的过程通信数据,与内存中的数据进行映射的函数,这个函数不需要自己写,说实话如果靠我们自己写,要对EtherCAT协议栈深入理解才行。所幸SSC生成的代码中已经帮我写好了,但是并不归属于协议栈,在项目文件中有其实现,我这边项目命名为STM32_EtherCAT_Slave,因此在STM32_EtherCAT_Slave.c中可以找到,复制粘贴到main.c中即可。

2.4.2 APPL_InputMapping(UINT16 *pData)

这个函数是输入数据映射函数。这里的【输入】是相对于主站来说,也就是我们这里从站的输出数据。我们这里定义了输入数据是64个bit,因此这里就要将内存中64个bit的数据赋值给pData指针指向的首地址。
又因为我们在SSC配置的Excel中,输入数据定义的是InputS0x6000地址,因此在我的工程文件STM32_EtherCAT_SlaveObjects.h中就会有INPUTS0x6000这个变量的定义。
那么这个函数的实现也就不难了,深入理解C语言指针的同学一定不难看懂以下代码:

void APPL_InputMapping(UINT16 *pData) {
    *pData++ = (((UINT16 *) &INPUTS0x6000)[1]);
    *pData++ = (((UINT16 *) &INPUTS0x6000)[2]);
    *pData++ = (((UINT16 *) &INPUTS0x6000)[3]);
    *pData = (((UINT16 *) &INPUTS0x6000)[4]);
}
2.4.3 APPL_OutputMapping(UINT16 *pData)

同理,该函数这么编写:

void APPL_OutputMapping(UINT16 *pData) {
    ((UINT16 *) &OUTPUTS0x7000)[1] = (*pData++);
    ((UINT16 *) &OUTPUTS0x7000)[2] = (*pData++);
    ((UINT16 *) &OUTPUTS0x7000)[3] = (*pData++);
    ((UINT16 *) &OUTPUTS0x7000)[4] = (*pData);
}

3.总结

至此STM32与LAN9252对接的所有接口都完成了,编译无误的话可以试着下载到设备中去。观察看看LAN9252的RUN灯是否可以常亮。
其实移植的最主要难点还在于是否能将一开始KEIL的所有报错,有耐心地一一排除掉。到了这一步对接接口,就好比一个萝卜一个坑,在正确的位置调用正确的API就可以了。
下一章,我们将结束这篇系列教程,在TwinCAT2上配置从站,并验证我们编写的代码。