本节说明如何把DM9000移植到eCos中。DM9000在redboot中工作方式为poll,但在eCos中则是使用中断工作方式,所以移植和调试的重点就是GPIO中断。
之前没有了解过STM32的GPIO中断,也不清楚在eCos中如何安装和使用GPIO中断,甚至在开始调试DM9000驱动的时候不知道要了解这块知识,所以整个调试过程都是在慢慢的摸索中掌握的。
所以本节与其说是移植DM9000驱动,还不如说是如何在eCos中安装和使用stm32的GPIO中断。需要注意的是,这里不会太详细介绍stm32 GPIO外部中断的知识,因为stm32的参考手册有详细的介绍。
确定DM9000中断有效电平
DM9000中断引脚触发电平由芯片PIN20引脚EECK决定,如下图所示:
我的板子是低电平触发中断,使用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地址、掩码和网关;
使用示例
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”,原因尚待查明。
推荐阅读相关文章:
- stm32移植ecos #23,ecos ethernet driver,DM9000A驱动移植(中)
- stm32移植ecos #22,ecos ethernet driver,DM9000A驱动移植(上)
- stm32移植ecos #34,ecos sd driver,SD卡驱动(4)
- stm32移植ecos #33,ecos sd driver,SD卡驱动(3)
- stm32移植ecos #31,ecos sd driver,SD卡驱动(1)
- stm32移植ecos #30,ecos i2s driver,音频驱动(下)
- stm32移植ecos #29,ecos i2s driver,音频驱动(中)
- stm32移植ecos #28,ecos i2s driver,音频驱动(上)