本节说明如何把DM9000移植到eCos中。DM9000在redboot中工作方式为poll,但在eCos中则是使用中断工作方式,所以移植和调试的重点就是GPIO中断。

之前没有了解过STM32的GPIO中断,也不清楚在eCos中如何安装和使用GPIO中断,甚至在开始调试DM9000驱动的时候不知道要了解这块知识,所以整个调试过程都是在慢慢的摸索中掌握的。

所以本节与其说是移植DM9000驱动,还不如说是如何在eCos中安装和使用stm32的GPIO中断。需要注意的是,这里不会太详细介绍stm32 GPIO外部中断的知识,因为stm32的参考手册有详细的介绍。

确定DM9000中断有效电平

DM9000中断引脚触发电平由芯片PIN20引脚EECK决定,如下图所示:

image

我的板子是低电平触发中断,使用stm32的PA1引脚。这点要搞清楚了,不然调试的时候又问自己:怎么中断不响应啊???我自己就范了这个毛病。

安装GPIO中断

初看上去,DM9000驱动初始化函数dm9000_init()中,已经安装好了中断,实际不然。那怎么安装STM32的GPIO外部中断呢?

可参考eCos中STM3210E_EVAL模板中microchip spi接口enc424j600网卡驱动,源文件是:enc424j600_spi.c(网卡驱动程序,enc424j600_spi_init()函数,安装和配置中断)和stm3210e_eval_eth_enc424j600.c(平台定义部分,设置GPIO中断引脚和相关中断寄存器)。

首先修改dm9000_init()函数的中断安装部分,修改后代码如下:

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
#ifdef _DEBUG_
    diag_printf("dm9000 use interrupt...\n");
#endif
    dm9000_set_int_io();// Added by reille 2013.05.11
    priv->interrupt = CYGNUM_HAL_INTERRUPT_EXTI1;// Added by reille 2013.05.11

    cyg_drv_interrupt_create(priv->interrupt,
                             0,
                             (cyg_addrword_t)sc,
                             dm9000_isr,
                             eth_drv_dsr,
                             &priv->interrupt_handle,
                             &priv->interrupt_object);
    cyg_drv_interrupt_attach(priv->interrupt_handle);

    // Added by reille 2013.05.11
    cyg_drv_interrupt_configure(priv->interrupt,
            DM9000_ETH_INTERRUPT_LEVEL_LOW, DM9000_ETH_INTERRUPT_EDGE_FALLING);

    cyg_drv_interrupt_acknowledge(priv->interrupt);
    cyg_drv_interrupt_unmask(priv->interrupt);
#endif // !CYGPKG_IO_ETH_DRIVERS_STAND_ALONE

dm9000_set_int_io()函数代码如下:

// Added start by reille 2013.05.11
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED

#define DM9000_ETH_INTERRUPT_LEVEL_HIGH    (true)
#define DM9000_ETH_INTERRUPT_LEVEL_LOW     (false)
#define DM9000_ETH_INTERRUPT_EDGE_RISING   (true)
#define DM9000_ETH_INTERRUPT_EDGE_FALLING  (false)

static void dm9000_set_int_io(void)
{
#define CYGHWR_HAL_DM9000_INTERRUPT_PIN   1
#define CYGHWR_HAL_DM9000_INTERRUPT_PORT  'A'

#define CYGHWR_HAL_DM9000_INT_PIN    CYGHWR_HAL_STM32_GPIO(A, 1, IN, FLOATING)
    CYGHWR_HAL_STM32_GPIO_SET( CYGHWR_HAL_DM9000_INT_PIN );

    cyg_uint32 cr;
    cyg_uint32 backupr;

    // Interrupt output from dm9000 is connected to one of the pins from ports A-G.
    // Here this pin is marshaled to External Interrupt Controller (EXTI).

#define DM9000_INTERRUPT_SOURCE_PIN  CYGHWR_HAL_DM9000_INTERRUPT_PIN
#define DM9000_EXTICR                ((DM9000_INTERRUPT_SOURCE_PIN / 4) * 4 + 8)
#define DM9000_SHIFT_VALUE           ((DM9000_INTERRUPT_SOURCE_PIN % 4) * 4)

#ifdef DEBUG
    diag_printf("%s(): Mapping external interrupt from P%c%d.\n",__FUNCTION__,
                CYGHWR_HAL_DM9000_INTERRUPT_PORT,
                DM9000_INTERRUPT_SOURCE_PIN);
#endif

    // Is AFIO clock enabled?
    HAL_READ_UINT32(CYGHWR_HAL_STM32_RCC + CYGHWR_HAL_STM32_RCC_APB2ENR , backupr );
    if (0 == (backupr & BIT_(CYGHWR_HAL_STM32_RCC_APB2ENR_AFIO))) {
        CYGHWR_HAL_STM32_CLOCK_ENABLE(CYGHWR_HAL_STM32_CLOCK(APB2,AFIO));
    }

    // Modify External Interrupt Control Register
    HAL_READ_UINT32(CYGHWR_HAL_STM32_AFIO + DM9000_EXTICR , cr );
    cr |= ((cyg_uint32)0xf << DM9000_SHIFT_VALUE);
    cr &= (((cyg_uint32)(CYGHWR_HAL_DM9000_INTERRUPT_PORT - 'A')
                            << DM9000_SHIFT_VALUE) & 0xffff);
    HAL_WRITE_UINT32(CYGHWR_HAL_STM32_AFIO + DM9000_EXTICR , cr );

    // Restore AFIO clock
    if (0 == (backupr & BIT_(CYGHWR_HAL_STM32_RCC_APB2ENR_AFIO))) {
        CYGHWR_HAL_STM32_CLOCK_DISABLE(CYGHWR_HAL_STM32_CLOCK(APB2,AFIO));
    }
}
#endif
// Added end by reille 2013.05.11

注意下,cyg_drv_interrupt_configure()传递的参数,参数使用了几个宏,从宏的名称应该就可以理解其中的含义。我现在是直接把dm9000_set_int_io()函数代码放在了DM9000驱动源文件中,你也可以像enc424j600网卡驱动那样实现,使结构更加清晰和自然。

配置LWIP

在eCos图形配置工具中,添加LWIP协议栈,并进行相关配置,我的配置内容如下:

  • 配置LWIP为Sequential mode(默认是Simple mode),让LWIP支持多线程,同时使LWIP的API支持标准的socket的编程。
  • 配置eth0 configuration选项,取消“Use DHCP”,并在Network configuration中配置默认的IP地址、掩码和网关;

image image

使用示例

ecos/packages/net/lwip_tcpip/current/tests目录下,有很多测试程序。我选择的是socket.c这个测试程序,它使用标准的socket编译接口,实现了一个TCP服务器(client发给它什么数据,TCP服务器就返回什么数据给client)。

注意,即使在PC机上用ping来测试网络,也必须要在eCos应用程序中调用cyg_lwip_sequential_init()接口(对应Sequential mode)或cyg_lwip_simple_init()接口(对应Simple mode)来启动lwip协议栈。

我的程序运行在外部RAM中,PC机上ping的时间稳定在2ms。延时还是有点大,正常情况下应该是<=1ms。

stm32 GPIO中断问题

当你在PC机上ping板子的时候,你会发现:ping了一段时间后,发现网络不通了。这是什么原因呢?这个问题,我查了好久,这应该是stm32仅支持GPIO边沿触发中断而不支持电平触发中断引起的。如下所述:

ping不通时,DM9000的中断引脚电平是低电平(低电平触发中断),但是eCos却不再接收数据包了,也就是说eCos中没有接受到中断。

初始,怀疑中断安装是不是有问题,但当我这时清除DM9000中断状态寄存器后,eCos又能接收到数据包了。后来网上搜索STM32 GPIO中断资料时,了解到STM32 GPIO不支持电平触发中断,仅仅支持边沿触发。

所以,不难解释ping一段时间后ping不通了的原因:

DM9000驱动接收数据包时(对应dm9000_poll()函数),首先会关闭中断,然后接收数据,接收完并处理数据后,然后才(在dm9000_deliver()函数中)打开中断。如果在DM9000驱动接收完数据后,且在打开中断之前,DM9000又接收到新的数据包,其中断引脚电平就会保持为有效的低电平直到清除DM9000中断状态寄存器为止,但这时没有打开中断啊,所以之后再也接受不到中断了(因为DM9000中断引脚再也没有边沿跳变了,除非清除状态寄存器)。

我现在的处理是:

当DM9000驱动接收完数据,并打开中断之后,再读下DM9000的中断状态寄存器,如果发现有接受到新数据包,则直接清除中断状态寄存器。代码如下:

static void
dm9000_deliver(struct eth_drv_sc *sc)
{
    struct dm9000 *priv = (struct dm9000 *)sc->driver_private;

    dm9000_poll(sc);

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
    cyg_drv_interrupt_unmask(priv->interrupt);
#endif

    // Added by gyr 2013.05.20
    // get and clear staus
    cyg_uint8 status = getreg(priv, DM_ISR);
    if (status & IMR_PRM) {
        putreg(priv, DM_ISR, status);
    }
}

这种处理不是很好:如果后面不来数据了怎么办?那这个数据包就会一直读不到,除非后面又来了新的数据包或者自己发送数据包(发送数据包也会产生中断)。

遗留问题

现在ping了一段时间后,会出现程序挂掉的问题:“$T05thread:00000001;0f:06e80068;0d:e8660268;#e5”,原因尚待查明。

» 文章出处: reille博客—http://velep.com , 如果没有特别声明,文章均为reille博客原创作品
» 郑重声明: 原创作品未经允许不得转载,如需转载请联系reille#qq.com(#换成@)
分享到:

 Leave a Reply

(必须)

(我会替您保密的)(必须)

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

   
© 2012 velep.com | reille blog | 管理| 粤ICP备15065318号-2| 谷歌地图| 百度地图| Suffusion theme|Sayontan Sinha

无觅相关文章插件,快速提升流量