2440+dm9000A裸机以太网通讯

开头的话,好吧,我只能说写驱动程序的人都是疯子,疯子才能进入这个领域,或者一开始你没疯,后来你疯了。真的,当问题出现的时候,有可能你无数次相信硬件没问题,是自己的程序有问题,也可能最后真的是硬件本来就是坏的。或者你到最后都没搞清楚到底是硬件的问题还是软件的问题。(按理说现在程序界这么火,说明底层支持着的硬件还是很可靠的,去找自己的问题吧!我也不知道我在说什么。

进入正题。

第一部分,如果你不知道单片机和DM9000网卡的以太网通讯思路,请赶紧百度上查阅DM9000的以太网设计,如果直接看DM9000数据手册,真的会看死人。建议先看看整体思路,再然后有时间最好通看一下DM9000的数据手册。

第二部分,2440单片机的知识至少要掌握中断和串口(当然越多越好)。搞清楚中断入口程序的地址和中断函数的联系。

第三部分,DM9000这部分程序的编写。主要部分分三块。

第一块,DM9000的初始化

void DM9000_init(void)
{
	uint32 i;
	Test_DM9000AE();
	IOSetInit();
	dm9000_reg_write(DM9000_IMR, 0x80);		//中断关闭
	//初始化设置步骤: 1
	dm9000_reg_write(DM9000_GPCR, 0x01);	//设置 GPCR(1EH) bit[0]=1,使DM9000的GPIO3为输出。
	dm9000_reg_write(DM9000_GPR,  0x00);	//GPR bit[0]=0 使DM9000的GPIO3输出为低以激活内部PHY。
	udelay(500);							//延时2ms以上等待PHY上电。
	
	//初始化设置步骤: 2
	dm9000_reg_write(DM9000_NCR,  0x03);	//软件复位
	udelay(300);								//延时20us以上等待软件复位完成
	dm9000_reg_write(DM9000_NCR,  0x00);	//复位完成,设置正常工作模式。
	dm9000_reg_write(DM9000_NCR,  0x03);	//第二次软件复位,为了确保软件复位完全成功。此步骤是必要的。
	udelay(300);
	dm9000_reg_write(DM9000_NCR,  0x00);
	//初始化设置步骤: 3
	dm9000_reg_write(DM9000_NSR,  0x2c);	//清除各种状态标志位
	dm9000_reg_write(DM9000_ISR,  0xbf);	//清除所有中断标志位
	//初始化设置步骤: 4
    <span style="white-space:pre">	</span>	
	//初始化设置步骤: 5
	for(i=0; i<6; i++)
		dm9000_reg_write(DM9000_PAR + i, mac_addr[i]);//mac_addr[]自己定义一下吧,6个字节的MAC地址
	
	//初始化设置步骤: 6
	dm9000_reg_write(DM9000_NSR,  0x2c);	//清除各种状态标志位
	dm9000_reg_write(DM9000_ISR,  0x3f);	//清除所有中断标志位
	//初始化设置步骤: 7
	dm9000_reg_write(DM9000_IMR, 0x81);		//中断使能}

第二块,发送程序的编写

void DM9000_sendPcket(uint8 *datas, uint32 length)
{
	uint32 len,i;
	//uint8 tmp;
	
	//Printf("发送数据\r\n");
	
	dm9000_reg_write(DM9000_IMR,0x80);		//先禁止网卡中断,防止在发送数据时被中断干扰	
	len = length;							//把发送长度写入
    dm9000_reg_write(DM9000_TXPLH, (len>>8) & 0x0ff);
    dm9000_reg_write(DM9000_TXPLL, len & 0x0ff);
	DM_ADD = DM9000_MWCMD;
    for(i=0; i<len; i+=2)					//16 bit mode
    {
        udelay(2);
        DM_CMD = datas[i] | (datas[i+1]<<8);
    }
	dm9000_reg_write(DM9000_TCR, 0x01);		//发送数据到以太网上
    
    while(1)//等待数据发送完成
    {
    	uint8 data;
    	data = dm9000_reg_read(DM9000_TCR);//DM9000_NSR
    	if((data&0x01) == 0x00) break;
    }
    /* tmp = dm9000_reg_read(DM9000_NSR);
   
    if((tmp & 0x01) == 0x04)
    {
    	if((dm9000_reg_read(DM9000_TSR1)&0xfc) == 0x00)
    		Printf("TSR1成功\r\n");
    	else
    		Printf("TSR1失败r\n");   	
    }
    else
    {
    	if((dm9000_reg_read(DM9000_TSR2)&0xfc) == 0x00)
    		Printf("TSR2成功\r\n");
    	else
    		Printf("TSR2失败r\n");
    }
    */
    dm9000_reg_write(DM9000_NSR, 0x2c);		//清除状态寄存器,由于发送数据没有设置中断,因此不必处理中断标志位
    dm9000_reg_write(DM9000_IMR, 0x81);		//DM9000网卡的接收中断使能
	//Printf("发送数据完成\r\n");
}

第三块,接受程序的编写,最难的就是接收程序了,因为这里涉及到中断,自己之前配置好外部中断,中断程序中接收DM9000的接收包。然后坑爹的是以太网帧开始位置的确定,到底从DM9000接收缓存的那个地方把数据复制到单片机内存中。手册上说的是(0x01,状态字节,长度低字节,长度高字节)这四个字节作为识别区,先用预读取指令读取缓冲区的字节,判断是0x00还是0x01,否则就复位DM9000网卡,尼玛坑爹就在这里啊,复位还是不好使啊。然后预读取完只能直接读取下面的字节了,但这样会导致读取的数据不对,可能读到的数据并不是以真正的目的物理地址开头的数据,可能错位,前移或者后移(我不知道是不是网卡初始化对不对)。所以我只能这么做了,直接检测0x01字节,加上判断目的物理地址是否正确来确定以太网帧的位置,以及这个帧是否是传给自己的。理论上这是可靠的。以下为代码。

void IOSetInit(void)
{
	rGPFCON = (rGPFCON & (~(0x03<<14))) | (0x02<<14);	//GPF7设置为EINT7
	rEXTINT0 = (rEXTINT0 & (~(0x07<<28))) | (0x01<<28);
	rEINTMASK = rEINTMASK & (~(0x01<<7));
	ClearPending(BIT_EINT4_7);
	pISR_EINT4_7 = (U32)Eint7_ISR;
	rINTMSK = rINTMSK & (~(BIT_EINT4_7));
}
static void __irq Eint7_ISR(void)
{
	uint32 i;
	uint16 type,m,n;
	//Printf("Eint7中断服务\r\n");
	//VAR_RETURN VARRETURN;
	//VAR_RETURN *var_re=&VARRETURN;
	Buffer[12]=0,Buffer[13]=0;
	len = receivepacket(Buffer);
	//后面的可以自己添加,这里省略
}



uint32 receivepacket(uint8 *datas)
{
	
	uint16 i,tmp,len=0,status=0;
	uint8 ready=0;
	 
	//ready = 0;								//希望读取到"01H"
	//status = 0;								//数据包状态
	//len = 0; 								    //数据包长度
	
	
    if(dm9000_reg_read(DM9000_ISR) & 0x01)			
    {
        dm9000_reg_write(DM9000_ISR, 0x01);	//清除接收中断标志位
    }
    else return 0;
	
	//else { Printf("你好\r\n");return 0;}
	
	//DM_ADD=DM9000_MRCMDX;
	//ready = DM_CMD; // 第一次读取,一般读取到的是 00H
    //Printf("预读取第一次:%x\r\n",ready);
	//ready = DM_CMD ; // 第一次读取,一般读取到的是 01H
	//Printf("预读取第二次:%x\r\n",ready);
	
	
    
    
    
    DM_ADD = DM9000_MRCMD;
   
    status=DM_CMD ;//读状态字节
    while((status&0xff)!=0x01)
    {
    	status=DM_CMD ;
    
    }
    //status=(status&0xff00)>>8;
    
	
	
    len = DM_CMD;								//读数据包长度
    
   
    //Printf("st=%x status=%x  len= %x\r\n",st,status,len);
    if( (len < 1522))//!(status & 0xbf) &&
    {
    	
        for(i=0; i<6; i+=2)// 这个FOR语句是为了判断数据包是否传给自己,即检查MAC地址
        {
            //udelay(20);
            tmp = DM_CMD;
            datas[i] = tmp & 0x0ff;
            if((datas[i]!=0xff)&&(datas[i]!=mac_addr[i])) return 0;
            datas[i + 1] = (tmp >> 8) & 0x0ff;
            if((datas[i+1]!=0xff)&&(datas[i+1]!=mac_addr[i+1])) return 0;
        }
        //Printf("状态字:%x\r\n",status);
       
       
        for(i=6; i<len+1; i+=2)// 16 bit mode
        {
            //udelay(20);
            tmp = DM_CMD;
            datas[i] = tmp & 0x0ff;
            datas[i + 1] = (tmp >> 8) & 0x0ff;
            
        }
  
        
    }
    else			return 0;
//	if(len > 1000) 	return 0;
//  if( (HON( ETHBUF->type ) != ETHTYPE_ARP) && (HON( ETHBUF->type ) != ETHTYPE_IP) )
//		return 0;

    	
	return len;
}


第四部分:TCP/IP的知识(需要自己查资料看)。这里就是对接收到的以太网帧进行解封,和在发送数据之前对数据包进行封装。鉴于TCP协议稍复杂,我暂时实现UDP协议。UDP数据包发送之前,需要先检查本地是否有接收方IP地址所对应的物理地址,如果没有,需要先发送ARP请求包(广播),得到应答包以后解析应答包得到物理地址。然后在加上接收方目的物理地址发送加上数据的UDP包(非广播)。所以这里至少要写ARP请求包和发送ARP应答包的程序,UDP的封装和解封程序(如果用UDP协议的话),当然也可以用TCP协议。

首先是TCP/IP一些报头的结构体:

typedef struct eth_hdr 			//以太网头部结构,为了以后使用方便
{
	uint8	d_mac[6];   		//目的地址
	uint8	s_mac[6];   		//源地址
	uint16	type;    			//协议类型
}ETH_HDR;

typedef struct arp 		       //ARP首部结构
{
					
	uint16	hwtype;     		//硬件类型(1表示传输的是以太网MAC地址)
	uint16	protocol;			//协议类型(0x0800表示传输的是IP地址)
	uint8	hwlen;				//硬件地址长度(6)
	uint8	protolen;			//协议地址长度(4)
	uint16	opcode; 			//操作(1表示ARP请求,2表示ARP应答)
	uint8	smac[6];			//发送端MAC地址
	uint8	sipaddr[4];			//发送端IP地址
	uint8	dmac[6];			//目的端MAC地址
	uint8	dipaddr[4];			//目的端IP地址
}ARP;

typedef struct ip			    //IP首部结构
{
	uint8	vhl;      			//4位版本号4位首部长度(0x45)
	uint8	tos;				//服务类型(0)
	uint16	len;				//整个IP数据报总字节长度
	uint16	ipid;           	//IP标识
	uint16	ipoffset;     		//3位标识13位偏移
	uint8 	ttl;             	//生存时间(32或64)
	uint8	proto;         		//协议(1表示ICMP,2表示IGMP,6表示TCP,17表示UDP)
	uint16 	ipchksum;    		//首部校验和
	uint8 	srcipaddr[4];    	//源IP
	uint8	destipaddr[4];   	//目的IP
}IP;

typedef struct tcp			    //IP首部结构
{
	uint16  sport;              //源端口号
	uint16  dport;				//目的端口号
	uint32  sequencenum;		//顺序号
	uint32  acknowledgenum;		//确认号
	uint8   tcplength;          //低四位为TCP报头字(32位)的个数,高四位必须为0,是保留位
	uint8   flags;    			//低两位为保留位,高6位为标志位[2:7]依次为
	                            //URG:紧急指针。用到的时候值为1,用来处理避免TCP数据流中断
	                            //ACK:置1时表示确认号为合法,为0的时候表示数据段不包含确认信息,确认号被忽略。  
								//PSH:置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送。
								//RST:用于复位因某种原因引起出现的错误连接,也用来拒绝非法数据和请求。
								//SYN:在连接请求中,SYN=1,ACK=0,连接响应时,SYN=1,ACK=1。
								//FIN:用来释放连接,表明发送方已经没有数据发送了。
	uint16  window;				//指定关于发送端能传输的下一段的大小的指令,表示想收到的每个TCP数据段的大小。
	uint16  tcpchksum;			//TCP校验和							
	uint16  urgentpoint;  		//紧急指针16位,紧急指针指出在本报文段中的紧急数据的位置,在URG标志设置了时才有效。							
								
								
								
}TCP;

typedef struct udp              //UDP首部结构
{
	uint16  sport;              //源端口号                        (34 35)
	uint16	dport;				//目的端口号                       (36 37)
	uint16  length;				//UDP数据包报总长度                (38 39)
	uint16  udpchksum;			//UDP校验和(可选项)                (40 41)
}UDP;


各层封装函数(解封函数就不贴了,比较灵活,不必要的情况下,可以简单判断一下就能知道各层是什么协议):

//这是一个物理层的程序,微数据加上MAC报头
//by hongfangyu 2016/1/9

#include "dm9000.h"

void MAC_pack(uint8 *datas,uint16 length,uint16 type)
{
	ETH_HDR *MACBUF;
	uint8 packhead[14];
	MACBUF=(ETH_HDR *)&packhead;
	memcpy(MACBUF->d_mac, host_mac_addr, 6);
	memcpy(MACBUF->s_mac, mac_addr, 6);
	MACBUF->type = H( type );	
	
	backwards(datas,length,14);								//数据包在Buffer中后移8字节
	memcpy(datas,packhead,14);								//将14字节MAC报头复制到Buffer前面
	
	packet_len=length+14;
	
	
}

//这是一个网络层的程序,为数据包加上IP或者ARP报头

//by hongfangyu 2016/1/9

#include"dm9000.h"

//函数:计算IP报头的校验和
uint16 ipchecksum(uint8 *dp)
{
	uint32 sum=0;
	uint8  di=0;
	for(di=0;di!=20;di=di+2) sum+=dp[di];
	sum=sum<<8;
	for(di=1;di!=21;di=di+2) sum+=dp[di];
	sum=(sum&0xffff)+(sum>>16);
	return ~((uint16)sum);


}


uint16 IP_pack(uint8 *datas,uint16 length,uint8 type)
{
	IP *IPBUF;
	uint8 packhead[20];
	IPBUF=(IP *)&packhead;
	IPBUF->vhl=0x45;
	IPBUF->tos=0;
	IPBUF->len=H(length+20);
	IPBUF->ipid=H(0x0);
	IPBUF->ipoffset=0;
	IPBUF->ttl=0x80;
	IPBUF->proto=type;
	IPBUF->ipchksum=0x0;									//检验和初始化为0
	memcpy(IPBUF->srcipaddr, ip_addr, 4);
	memcpy(IPBUF->destipaddr, host_ip_addr, 4);

	IPBUF->ipchksum=H(ipchecksum(packhead));
	
	backwards(datas,length,20);								//数据包在Buffer中后移20字节
	memcpy(datas,packhead,20);								//将20字节IP报头复制到Buffer前面
	
	packet_len=length+20;
	return 0x0800;											//将网络层的协议类型返回给下一层使用
	
}
uint16 ARP_pack(uint8 *datas,uint16 length,uint8 type)
{
	ARP *ARPBUF;
	uint8 packhead[28];
	ARPBUF=(ARP *)&packhead;
	ARPBUF->hwtype = H( 1 );								//硬件类型(1表示传输的是以太网MAC地址)
	ARPBUF->protocol = H( 0x0800 );							//协议类型(0x0800表示传输的是IP地址)
	ARPBUF->hwlen = 6;
	ARPBUF->protolen = 4;
	ARPBUF->opcode = H( type );
	memcpy(ARPBUF->smac, mac_addr, 6);
	memcpy(ARPBUF->sipaddr, ip_addr, 4);
	memcpy(ARPBUF->dipaddr, host_ip_addr, 4);
	
	
	//if(type==2)
	
	
	backwards(datas,length,28);								//数据包在Buffer中后移28字节
	memcpy(datas,packhead,28);								//将28字节IP报头复制到Buffer前面
	
	packet_len=length+28;
	
	return 0x0806;											//将网络层的协议类型返回给下一层使用
	
}
//这是一个传输层的程序
//主要完成了接受数据层和网络层的数据,然后对TCP和UDP报头的封装和分用






//函数:计算UDP检验和
uint16 udpchecksum(uint8 *head,uint8 *datah, uint16 lengthh)
{
	uint32 summ=0,suml=0,sumh=0;
	uint16 pi=0;

	//UDP伪首部和首部校验和
	//这个伪首部指,源地址、目的地址、UDP数据长度、协议类型(0x11),协议类型就一个字节,但需要补一个字节的0x0,构成12个字节。
	suml=ip_addr[1]+ip_addr[3]+host_ip_addr[1]+host_ip_addr[3]+head[5]+0x11    +head[1]+head[3]+head[5]+head[7];
	sumh=ip_addr[0]+ip_addr[2]+host_ip_addr[0]+host_ip_addr[2]+head[4]+   0    +head[0]+head[2]+head[4]+head[6];

	//数据部分校验和
	for(pi=1;pi!=lengthh+1;pi+=2)
		suml=suml+datah[pi];
	for(pi=0;pi!=lengthh;pi+=2)
		sumh=sumh+datah[pi];
	
	//UDP伪首部+UDP首部+数据 一起计算校验和,校验和为低16位,高出16位部分右移16位后加到原来低16位上
	summ=(sumh<<8)+suml;
	summ=(summ&0xffff)+(summ>>16);
	
	//取反过程,并返回
	return (~(uint16)summ);

}
uint8 UDP_pack(uint8 *datas,uint16 length)
{
	UDP *UDPBUF;
	uint8 packhead[8];
	UDPBUF =(UDP*)&packhead;								//这里用不用取址符号都是对的,正常理解应该不要取址符号
	UDPBUF->sport=H(1024);									//源端口号设置 1024=0x0400,直接写1024的话,小端模式中第一个字节为0,第二个字节为4,
															//但是接收方会认为先收到的为高位,结果成了0x0004,所以应该先互换高低位写进去
	
	UDPBUF->dport=H(2844);									//目的端口号设置
	UDPBUF->length=H(length+8);								//UDP包长度=数据部分长度+UDP报头长度
	UDPBUF->udpchksum=0;									//UDP校验和初始化为0
	
	UDPBUF->udpchksum=H(udpchecksum(packhead,datas,length));	//UDP检验和
	
	backwards(datas,length,8);								//数据包在Buffer中后移8字节
	memcpy(datas,packhead,8);								//将8字节UDP报头复制到Buffer前面
	
	packet_len=length+8;
	
	return 0x11;											//返回传输层的协议为UDP,以供下一层使用
	
	
}

第五部分 之后就可以根据具体的项目需要进行不同的设计了,可以自己定义以太网帧中数据部分的协议,进行数据的交换。


结尾的话。。。。。。师兄一:这年头会一门语言真的挺重要的。

师兄二:没关系,我会中文就已经够努力了,还得天天看英文论文。

师兄一:你觉得学C++好还是学JAVA好?

师兄二:我选择狗带。。。。。。






        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值