一步步移植uCOS-II and LwIP (一)

STM32F103ZE下移植uCOS-II and LwIP 汇总

本文主要记录嵌入式实时操作系统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

一、Lwip移植

TCP/IP协议分为网络链路层、网络层、传输层和应用层四个部分,网络链路层主要涉及底层硬件驱动的编写,另外三个上次协议一般采用协议栈的方式软件实现。要实现与其它网络设备通信,首当其冲的是要移植好底层网卡驱动。LwIP协议栈已经为我们提供了网络链路底层接口,我们要做到主要工作涉及到以下几个方面:
- 单片机与网卡DM9000芯片的通信;
- 完善LwIP协议栈文件ethernetif.c接口函数,该部分的难点在于实现LwIP规定的struct pbuf类型的数据包与网卡数据之间相互转换;
- 上层软件协议的simple explain

1、网卡DM9000底层驱动的编写

首先查阅DM9000的Datasheet(建议直接从芯片官网上查找,一般会有该芯片的Application note or Demo
一步步移植uCOS-II and LwIP (一)_第1张图片
本文主要是运用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;
}

一步步移植uCOS-II and LwIP (一)_第2张图片
DM9000 物理层PHY寄存器读写及验证如下:

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;
}

一步步移植uCOS-II and LwIP (一)_第3张图片
DM9000初始化配置如下:

#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();
    }
}

一步步移植uCOS-II and LwIP (一)_第4张图片
一步步移植uCOS-II and LwIP (一)_第5张图片

从输出结果看出网卡DM9000初始化完毕后,网口状态显示已经连接,并且连接速度为100Mbps FULL-DUPLEX。但连接指示并未变成蓝色,表示PC机和硬件平台之间无数据传输,这是因为我们仅只实现了底层网络链路层的缘故。下一小节我们重点关注ethernetif.c文件中网口数据传送部分,这也是移植网卡驱动的难点。

注:本文仅列出核心代码,依照这些代码,便足以自己动手完成移植工作。

你可能感兴趣的:(网卡知识点)