本文主要记录嵌入式实时操作系统uCOS-II(Ver 2.85)和轻量型TCP/IP协议栈LwIP(Ver 1.4.1)在32bit单片机STM32F103ZE上的移植过程,并列举几个simple examples 说明两者工作原理。本文的叙述原则是“用到什么知识点,就查阅相关资料”,对于其它延伸知识点不再概述。
所需物资:
- 硬件开发平台,本平台网卡为DM9000A
- uCOS-II(Ver 2.85)源码,可直接从Micrium官网下载uCOS在cortex-M3上的移植例程uCOSII-ST-STM32F103ZE-SK
- LwIP(Ver 1.4.1)源码,下载链接LwIP1.4.1
TCP/IP协议分为网络链路层、网络层、传输层和应用层四个部分,网络链路层主要涉及底层硬件驱动的编写,另外三个上次协议一般采用协议栈的方式软件实现。要实现与其它网络设备通信,首当其冲的是要移植好底层网卡驱动。LwIP协议栈已经为我们提供了网络链路底层接口,我们要做到主要工作涉及到以下几个方面:
- 单片机与网卡DM9000芯片的通信;
- 完善LwIP协议栈文件ethernetif.c接口函数,该部分的难点在于实现LwIP规定的struct pbuf类型的数据包与网卡数据之间相互转换;
- 上层软件协议的simple explain;
首先查阅DM9000的Datasheet(建议直接从芯片官网上查找,一般会有该芯片的Application note or Demo)
本文主要是运用DM9000的16-bit mode,其总线形式类似与intel 8080总线,涉及读写指令和数据的控制引脚为CS#、IOW#、IOR#、CMD,数据总线引脚SD0~SD15,中断引脚INT。通常我们可以直接采用单片机I/O软件模拟时序来访问DM9000,但STM32内部自带FSMC控制器,可把DM9000当作SRAM器件使用地址访问,减少了不少功夫。关于STM32的FSMC,可详细参阅STM32的Reference Munual。DM9000数据手册介绍CMD引脚是控制读写数据和指令(寄存器)的开关标志,CMD=1,DATA port,CMD=0,INDEX port。所以可以将CMD接入FSMC的某一根地址线上(本文的硬件平台接入的是FSMC-A2),通过地址高低位来区别开写入的数据还是寄存器索引号,其实现数据结构如下:
#define DM9000_BASE ((uint32_t)0x6c100006)
typedef struct
{
uint16_t REG;
uint16_t DATA;
}DM9000_TypeDef;
#define DM9000 ((DM9000_TypeDef *)DM9000_BASE)
//or two methods
#define DM9000_REG *(volatile uint16_t*)((uint32_t)0x6c100000)
#define DM9000_DAT *(volatile uint16_t*)((uint32_t)0x6c100008) // FSMC A2 -> HADDR[3]
用类似于ST固件库的编写形式来定义设备DM9000,实现对地址的访问,注意16-bit mode下FSMC A[24:0] <=>HADDR[25:1]>>1。单片机的FSMC配置程序如下:
void DM9000_Configuration(void) //config fsmc to adapt dm9000
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef FSMC_ReadWriteTimingStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOA| \
RCC_APB2Periph_GPIOG,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
//FSMC D0~D15 NWE and NOE
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15 | GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8| GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11| GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15 | GPIO_Pin_3 | GPIO_Pin_4; //FSMC A19 A20
GPIO_Init(GPIOE,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //NE4;
GPIO_Init(GPIOG,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //FSMC A2
GPIO_Init(GPIOF,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 INT
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_Pin_1);
EXTI_InitStructure.EXTI_Line = EXTI_Line1 ;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
EXTI_ClearITPendingBit(EXTI_Line1);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE; // interrupt is not used
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
FSMC_ReadWriteTimingStructure.FSMC_AddressSetupTime = 0x00;
FSMC_ReadWriteTimingStructure.FSMC_AddressHoldTime = 0x00;
FSMC_ReadWriteTimingStructure.FSMC_DataSetupTime = 0x03;
FSMC_ReadWriteTimingStructure.FSMC_BusTurnAroundDuration = 0x00;
FSMC_ReadWriteTimingStructure.FSMC_CLKDivision = 0x00;
FSMC_ReadWriteTimingStructure.FSMC_DataLatency = 0x00;
FSMC_ReadWriteTimingStructure.FSMC_AccessMode = FSMC_AccessMode_A;
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst =FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &FSMC_ReadWriteTimingStructure;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &FSMC_ReadWriteTimingStructure;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
}
DM9000寄存器读写配置程序如下:
void DM9000_WriteReg(uint16_t dat, uint16_t address)
{
DM9000->REG = address;
DM9000->DATA = dat;
}
uint16_t DM9000_ReadReg(uint16_t address)
{
DM9000->REG = address;
return DM9000->DATA;
}
读取DM9000的ID,验证DM9000读写寄存器配置,程序及串口输出如下:
uint32_t DM9000_GetVPID(void)
{
uint32_t vpid;
vpid = (DM9000_ReadReg(DM9000_VIDH)<<24)(DM9000_ReadReg(DM9000_VIDL)<<16)|
(DM9000_ReadReg(DM9000_PIDH)<<8)|(DM9000_ReadReg(DM9000_PIDL));
return vpid;
}
void DM9000_WritePHYReg(uint16_t dat, uint8_t phy_offset)
{
DM9000_WriteReg(0x40|phy_offset,DM9000_EPAR);
DM9000_WriteReg((dat&0xff00)>>8, DM9000_EPDRH);
DM9000_WriteReg(dat&0xff, DM9000_EPDRL);
DM9000_WriteReg(0x0A, DM9000_EPCR);
delay_ms(150);
while((DM9000_ReadReg(DM9000_EPCR)&0x01)==0x01);
DM9000_WriteReg(0x00, DM9000_EPCR);
}
uint16_t DM9000_ReadPHYReg(uint8_t phy_offset)
{
uint16_t temp;
DM9000_WriteReg(0x40|phy_offset,DM9000_EPAR);
DM9000_WriteReg(0x0c, DM9000_EPCR);
delay_ms(20);
while((DM9000_ReadReg(DM9000_EPCR)&0x01) == 0x01);
DM9000_WriteReg(0x00, DM9000_EPCR);
temp = (DM9000_ReadReg(DM9000_EPDRH)<<8)|(DM9000_ReadReg(DM9000_EPDRL));
return temp;
}
uint32_t DM9000_GetPHYID(void)
{
uint32_t temp;
temp = ((uint32_t)(DM9000_ReadPHYReg(DM9000_PHY_PHYID1))<<16)| (DM9000_ReadPHYReg(DM9000_PHY_PHYID2));
return temp;
}
#define DM9000_IDENTIFIER (uint32_t)(0x0a469000)
#define DM9000_PHYID (uint32_t)(0x0181b8a0)
uint8_t DM9000_Init(void) //1:error
{
DM9000_Configuration();
DM9000_Reset();
if(DM9000_GetVPID() == DM9000_IDENTIFIER)
printf("dm9000's fsmc configuraion is right!\n");
else
return 1;
if(DM9000_GetPHYID() == DM9000_PHYID)
printf("dm9000's phy configuraion is right!\n");
else
return 1;
DM9000_SetPHYMode(PHYMODE_AUTO_NEGOTIATION);
DM9000_MacAddressSet();
DM9000_MulticastAddressSet();
DM9000_WriteReg(IMR_PAR,DM9000_IMR); // shut interrupt
DM9000_WriteReg(0x80,DM9000_TCR2); // setup led mode
DM9000_WriteReg(0x00,DM9000_GPR); // power on PHY
DM9000_WriteReg(ISR_CLR_STATUS, DM9000_ISR);
DM9000_GetPHYMode();
DM9000_WriteReg(IMR_PAR|IMR_PRI, DM9000_IMR); // rx interrupt enable
DM9000_WriteReg(RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN, DM9000_RCR); // begin receive
return 0;
}
// 获取PHY工作模式
void DM9000_GetPHYMode(void)
{
uint8_t temp;
uint16_t autonegotiationflag;
uint8_t i=0;
autonegotiationflag = DM9000_ReadPHYReg(DM9000_PHY_BMCR)&PHYMODE_AUTO_NEGOTIATION;
if(autonegotiationflag == PHYMODE_AUTO_NEGOTIATION)
{
printf("PHY works in auto_negotiation mode!\n");
while(!(DM9000_ReadPHYReg(DM9000_PHY_BMSR)&0x0020))
// wait for auto negatiation completed
{
i++;
if(i>200)
{
temp = 0xff; //failed
break;
}
}
}
else
{
printf("PHY works in setup mode!\n");
// wait for linking
{
i++;
if(i>200)
{
temp = 0xff; //failed
break;
}
}
}
temp = (DM9000_ReadReg(DM9000_NSR)>>6)&0x02;
// speed 100mbps or 10mbps
temp |= (DM9000_ReadReg(DM9000_NCR)>>3)&0x01;
// full-duplex or half-duplex
switch(temp)
{
case 0x00:
printf("PHY works in 100Mbps half-duplex mode!\n");
break;
case 0x01:
printf("PHY works in 100Mbps full-duplexmode!\n");
break;
case 0x02:
printf("PHY works in 10Mbps half-duplex mode!\n");
break;
case 0x03:
printf("PHY works in 10Mbps full-duplex mode!\n");
break;
default :
printf("PHY linked failed!\n");
DM9000_Reset();
}
}
从输出结果看出网卡DM9000初始化完毕后,网口状态显示已经连接,并且连接速度为100Mbps FULL-DUPLEX。但连接指示并未变成蓝色,表示PC机和硬件平台之间无数据传输,这是因为我们仅只实现了底层网络链路层的缘故。下一小节我们重点关注ethernetif.c文件中网口数据传送部分,这也是移植网卡驱动的难点。
注:本文仅列出核心代码,依照这些代码,便足以自己动手完成移植工作。