开头的话,好吧,我只能说写驱动程序的人都是疯子,疯子才能进入这个领域,或者一开始你没疯,后来你疯了。真的,当问题出现的时候,有可能你无数次相信硬件没问题,是自己的程序有问题,也可能最后真的是硬件本来就是坏的。或者你到最后都没搞清楚到底是硬件的问题还是软件的问题。(按理说现在程序界这么火,说明底层支持着的硬件还是很可靠的,去找自己的问题吧!我也不知道我在说什么。
进入正题。
第一部分,如果你不知道单片机和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
//初始化设置步骤: 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("发送数据rn");
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 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成功rn"); else Printf("TSR1失败rn"); } else { if((dm9000_reg_read(DM9000_TSR2)&0xfc) == 0x00) Printf("TSR2成功rn"); else Printf("TSR2失败rn"); } */ dm9000_reg_write(DM9000_NSR, 0x2c); //清除状态寄存器,由于发送数据没有设置中断,因此不必处理中断标志位 dm9000_reg_write(DM9000_IMR, 0x81); //DM9000网卡的接收中断使能 //Printf("发送数据完成rn"); } 第三块,接受程序的编写,最难的就是接收程序了,因为这里涉及到中断,自己之前配置好外部中断,中断程序中接收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中断服务rn"); //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("你好rn");return 0;} //DM_ADD=DM9000_MRCMDX; //ready = DM_CMD; // 第一次读取,一般读取到的是 00H //Printf("预读取第一次:%xrn",ready); //ready = DM_CMD ; // 第一次读取,一般读取到的是 01H //Printf("预读取第二次:%xrn",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= %xrn",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("状态字:%xrn",status); for(i=6; i //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);
上一篇:mini2440 裸机编程 -led
下一篇:一起学mini2440裸机开发(五)--定时器0的基础实验
推荐阅读最新更新时间:2024-11-12 22:46
设计资源 培训 开发板 精华推荐
- LTC3810-5 的典型应用 - 60V 电流模式同步开关稳压控制器
- SEN-15440,SparkFun 大气传感器突破 - BME280 (Qwiic)
- 使用 Analog Devices 的 LTC1143CS 的参考设计
- NCV890103MWGEVB:汽车开关稳压器、降压、1.2 A、2 MHz、复位、可调延迟评估板
- AZ431B精密5V/1A稳压器典型应用
- 【涂鸦智能】物联网温湿度计
- 用于汽车的 1.8V、3.3V DC 至 DC 多输出电源
- esp32-wroom/wrover ch340c
- 使用 Analog Devices 的 LTC1434CGN 的参考设计
- USB3.0测试板