【uip移植】在AVR单片机ATMega16A上运行uip协议栈,网卡使用ENC28J60

开发环境:Atmel Studio 7.0

使用的开发板:锐志51单片机开发板+AVR转接座

单片机:40脚直插的ATMega16A

Flash容量:16KB

SRAM容量:1KB(这个是最紧缺的资源)

晶振:11.0592MHz,熔丝位配置为EE C1(使用外部晶振)

如果换用其他的晶振,除了串口的波特率寄存器以及两个定时器的定时重装值需要修改以外,其他应该都不需要修改。

uip版本:uip-1.0

通过uip协议栈和ENC28J60网卡实现一个非常简单的HTTP服务器。

工程下载地址:https://pan.baidu.com/s/1slLXFTb


程序编译后的大小如下:(开了UDP功能)

开了串口输出:
Program Memory Usage : 8912 bytes   54.4 % Full
Data Memory Usage    : 874 bytes   85.4 % Full
没开串口输出:
Program Memory Usage : 8272 bytes   50.5 % Full
Data Memory Usage    : 874 bytes   85.4 % Full


【接线方式】

把ENC28J60上的SPI接到AVR单片机相应的SPI接口上,共4根线:

PB4 CS

PB5 MOSI

PB6 MISO

PB7 SCK

网卡中断输出接PB2(外部中断2)


【uip部分】

首先,新建一个空白项目,项目名称为ENC28J60,先解压下载的uip-master.zip文件,打开“我的文档/Atmel Studio/7.0/ENC28J60/ENC28J60/”文件夹,把解压出来的uip文件夹复制进去,进入复制后的uip文件夹,删掉Makefile.include,然后把解压出来的unix/clock-arch.h(注意是.h)和unix/uip-conf.h都复制到uip文件夹里面,并把uip文件夹添加到项目中。

打开uipopt.h,找到127行的UIP_FIXEDETHADDR,把它注释掉:

//#define UIP_FIXEDETHADDR 0 // 注释掉!

打开clock-arch.h,把CLOCK_CONF_SECOND由1000改为50:

#define CLOCK_CONF_SECOND 50 // 1秒=50*20ms


打开uip-conf.h文件,按下面的中文提示修改代码:

/**
 * \addtogroup uipopt
 * @{
 */

/**
 * \name Project-specific configuration options
 * @{
 *
 * uIP has a number of configuration options that can be overridden
 * for each project. These are kept in a project-specific uip-conf.h
 * file and all configuration names have the prefix UIP_CONF.
 */

/*
 * Copyright (c) 2006, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the uIP TCP/IP stack
 *
 * $Id: uip-conf.h,v 1.6 2006/06/12 08:00:31 adam Exp $
 */

/**
 * \file
 *         An example uIP configuration file
 * \author
 *         Adam Dunkels <adam@sics.se>
 */

#ifndef __UIP_CONF_H__
#define __UIP_CONF_H__

#include <inttypes.h>

/**
 * 8 bit datatype
 *
 * This typedef defines the 8-bit type used throughout uIP.
 *
 * \hideinitializer
 */
typedef uint8_t u8_t;

/**
 * 16 bit datatype
 *
 * This typedef defines the 16-bit type used throughout uIP.
 *
 * \hideinitializer
 */
typedef uint16_t u16_t;

/**
 * Statistics datatype
 *
 * This typedef defines the dataype used for keeping statistics in
 * uIP.
 *
 * \hideinitializer
 */
typedef unsigned short uip_stats_t;

/**
 * Maximum number of TCP connections.
 *
 * \hideinitializer
 */
#define UIP_CONF_MAX_CONNECTIONS 3 // 原值40, 每个占用35字节内存。由于40*35=1400B>1KB, 因此这个值必须改小

/**
 * Maximum number of listening TCP ports.
 *
 * \hideinitializer
 */
//#define UIP_CONF_MAX_LISTENPORTS 40 // 使用uip默认的值就行了, 不用自定义。每个占2字节

/**
 * uIP buffer size.
 *
 * \hideinitializer
 */
#define UIP_CONF_BUFFER_SIZE     420

/**
 * CPU byte order.
 *
 * \hideinitializer
 */
#define UIP_CONF_BYTE_ORDER      LITTLE_ENDIAN

/**
 * Logging on or off
 *
 * \hideinitializer
 */
#define UIP_CONF_LOGGING         0 // 关闭日志功能

/**
 * UDP support on or off
 *
 * \hideinitializer
 */
#define UIP_CONF_UDP             1 // 开UDP功能

/**
 * UDP checksums on or off
 *
 * \hideinitializer
 */
#define UIP_CONF_UDP_CHECKSUMS   1

/**
 * uIP statistics on or off
 *
 * \hideinitializer
 */
#define UIP_CONF_STATISTICS      1

/* Here we include the header file for the application(s) we use in
   our project. */
// 把这下面的文件包含都注释掉
/*#include "smtp.h"*/
/*#include "hello-world.h"*/
/*#include "telnetd.h"*/
/*#include "webserver.h"*/
/*#include "dhcpc.h"*/
/*#include "resolv.h"*/
/*#include "webclient.h"*/

// 添加以下类型定义
typedef int uip_tcp_appstate_t;
typedef int uip_udp_appstate_t;

// 回调函数的定义
#define UIP_APPCALL uip_appcall
#define UIP_UDP_APPCALL uip_udp_appcall
#define tcpip_output()
void uip_appcall(void);
void uip_udp_appcall(void);

#include <stdio.h> // 包含printf的定义

// 配置MAC地址
#define UIP_FIXEDETHADDR 1
#define UIP_ETHADDR0 0x42 // 第一个字节必须为偶数才是单播MAC地址
#define UIP_ETHADDR1 0x52
#define UIP_ETHADDR2 0x4d
#define UIP_ETHADDR3 0x4e
#define UIP_ETHADDR4 0x45
#define UIP_ETHADDR5 0x54

#endif /* __UIP_CONF_H__ */

/** @} */
/** @} */

【网卡驱动部分】

ENC28J60.h:
#ifndef ENC28J60_H_
#define ENC28J60_H_

// 注: ISP下载口不使用SPI的片选端SS
#define ENC28J60_CS0 (PORTB &= ~_BV(PORTB4))
#define ENC28J60_CS1 (PORTB |= _BV(PORTB4))

// ENC28J60网卡本身有一个bug: 如果开机后不久数据包就来了, 只读寄存器ERXWRPT可能来不及自动更新
// 导致收到的数据包从0地址开始写入, 而没有写入指定的接收缓冲区起始点
// 解决办法有两个: 1. 读取数据包时判断ERXWRPT是不是在发送缓冲区里面, 如果是, 则直接丢弃数据包, 并重写ERXSTL寄存器
// 2.索性将发送缓冲区的起始点直接设为0, 这样不管ERXWRPT有没有自动更新, 都不会出错(这里采用这种方法)
#define ENC_RECV_END 0x19fe // 接收缓冲区终点
#define ENC_SEND_START (ENC_RECV_END + 1) // 发送缓冲区起点

/* Key Registers */
#define EIE 0x1b
#define EIE_INTIE _BV(7) // 是否输出中断
#define EIE_PKTIE _BV(6)
#define EIE_DMAIE _BV(5)
#define EIE_LINKIE _BV(4)
#define EIE_TXIE _BV(3)
#define EIE_TXERIE _BV(1)
#define EIE_RXERIE _BV(0)
#define EIR 0x1c
#define EIR_PKTIF _BV(6)
#define EIR_DMAIF _BV(5)
#define EIR_LINKIF _BV(4)
#define EIR_TXIF _BV(3)
#define EIR_TXERIF _BV(1)
#define EIR_RXERIF _BV(0)
#define ESTAT 0x1d
#define ESTAT_CLKRDY _BV(0)
#define ECON2 0x1e
#define ECON2_AUTOINC _BV(7)
#define ECON2_PKTDEC _BV(6)
#define ECON2_PWRSV _BV(5)
#define ECON2_VRPS _BV(3)
#define ECON1 0x1f
#define ECON1_TXRST _BV(7)
#define ECON1_RXRST _BV(6)
#define ECON1_DMAST _BV(5)
#define ECON1_CSUMEN _BV(4)
#define ECON1_TXRTS _BV(3)
#define ECON1_RXEN _BV(2)
#define ECON1_BSEL 0x03

/* Bank 0 */
#define ERDPTL 0x00
#define ERDPTH 0x01
#define EWRPTL 0x02
#define EWRPTH 0x03
#define ETXSTL 0x04
#define ETXSTH 0x05
#define ETXNDL 0x06
#define ETXNDH 0x07
#define ERXSTL 0x08
#define ERXSTH 0x09
#define ERXNDL 0x0a
#define ERXNDH 0x0b
#define ERXRDPTL 0x0c
#define ERXRDPTH 0x0d
#define ERXWRPTL 0x0e
#define ERXWRPTH 0x0f
#define EDMASTL 0x10
#define EDMASTH 0x11
#define EDMANDL 0x12
#define EDMANDH 0x13
#define EDMADSTL 0x14
#define EDMADSTH 0x15
#define EDMACSL 0x16
#define EDMACSH 0x17

/* Bank 1 */
#define EHT0 0x00
#define EHT1 0x01
#define EHT2 0x02
#define EHT3 0x03
#define EHT4 0x04
#define EHT5 0x05
#define EHT6 0x06
#define EHT7 0x07
#define EPMM0 0x08
#define EPMM1 0x09
#define EPMM2 0x0a
#define EPMM3 0x0b
#define EPMM4 0x0c
#define EPMM5 0x0d
#define EPMM6 0x0e
#define EPMM7 0x0f
#define EPMCSL 0x10
#define EPMCSH 0x11
#define EPMOL 0x14
#define EPMOH 0x15
#define ERXFCON 0x18
#define EPKTCNT 0x19

/* Bank 2 */
// 以M开头的寄存器地址最高位应标记为1 (读取时需要跳过dummy byte)
#define MACON1 0x80
#define MACON1_TXPAUS _BV(3)
#define MACON1_RXPAUS _BV(2)
#define MACON1_PASSALL _BV(1)
#define MACON1_MARXEN _BV(0)
#define MACON3 0x82
#define MACON3_PADCFG 0xe0
#define MACON3_PADCFG_2 _BV(7)
#define MACON3_PADCFG_1 _BV(6)
#define MACON3_PADCFG_0 _BV(5)
#define MACON3_TXCRCEN _BV(4)
#define MACON3_PHDREN _BV(3)
#define MACON3_HFRMEN _BV(2)
#define MACON3_FRMLNEN _BV(1)
#define MACON3_FULDPX _BV(0)
#define MACON4 0x83
#define MACON4_DEFER _BV(6)
#define MACON4_BPEN _BV(5)
#define MACON4_NOBKOFF _BV(4)
#define MABBIPG 0x84 // MAC Back-to-Back Inter-Packet Gap Register
#define MAIPGL 0x86 // Non-Back-to-Back Inter-Packet Gap Low Byte
#define MAIPGH 0x87
#define MACLCON1 0x88
#define MACLCON2 0x89
#define MAMXFLL 0x8a
#define MAMXFLH 0x8b
#define MICMD 0x92
#define MICMD_MIISCAN _BV(1)
#define MICMD_MIIRD _BV(0)
#define MIREGADR 0x94
#define MIWRL 0x96
#define MIWRH 0x97
#define MIRDL 0x98
#define MIRDH 0x99

/* Bank 3 */
#define MAADR5 0x80
#define MAADR6 0x81
#define MAADR3 0x82
#define MAADR4 0x83
#define MAADR1 0x84
#define MAADR2 0x85
#define EBSTSD 0x06
#define EBSTCON 0x07
#define EBSTCSL 0x08
#define EBSTCSH 0x09
#define MISTAT 0x8a
#define EREVID 0x12
#define ECOCON 0x15
#define EFLOCON 0x17
#define EPAUSL 0x18
#define EPAUSH 0x19
#define MISTAT_BUSY _BV(0)

/* PHY Registers */
#define PHCON1 0x00
#define PHCON1_PRST _BV(15) // PHY Software Reset
#define PHCON1_PLOOPBK _BV(14) // PHY Loopback
#define PHCON1_PPWRSV _BV(11) // PHY Power-Down
#define PHCON1_PDPXMD _BV(8) // PHY Duplex Mode
#define PHSTAT1 0x01
#define PHID1 0x02
#define PHID2 0x03
#define PHCON2 0x10
#define PHSTAT2 0x11
#define PHSTAT2_LSTAT _BV(10)
#define PHIE 0x12
#define PHIE_PLNKIE _BV(4)
#define PHIE_PGEIE _BV(1)
#define PHIR 0x13
#define PHLCON 0x14

#define ENCCLR 0xa0
#define ENCSET 0x80
#define ENC28J60_GetBank() (ENC28J60_Read(ECON1) & ECON1_BSEL) // 获取当前Bank号
#define ENC28J60_IsPluggedIn() ((ENC28J60_ReadPhy(PHSTAT2) & PHSTAT2_LSTAT) != 0) // 判断网卡是否插有网线(并接通)
#define ENC28J60_WriteBufferByte(data) ENC28J60_SetBits(0, data, 0x7a) // 向缓冲区写入单个字节
#define SPI_Read() SPI_Write(0xff)

void ENC28J60_Init(void);
uint8_t ENC28J60_Read(uint8_t addr);
void ENC28J60_ReadBuffer(uint8_t *buf, uint16_t len);
uint16_t ENC28J60_ReadPhy(uint8_t addr);
void ENC28J60_SelectBank(uint8_t bank);
void ENC28J60_SetBits(uint8_t addr, uint8_t mask, uint8_t value);
void ENC28J60_SystemReset(void);
void ENC28J60_Write(uint8_t addr, uint8_t value);
void ENC28J60_WriteBuffer(uint8_t *data, uint16_t len);
void ENC28J60_WritePhy(uint8_t addr, uint16_t value);
uint8_t SPI_Write(uint8_t data);

#endif /* ENC28J60_H_ */

ENC28J60.c:
#include <avr/io.h>
#include <avr/sfr_defs.h>
#include "uip/uip.h"
#include "ENC28J60.h"

// 注意: 执行这些函数时一定要先关闭网卡中断!!! 防止SPI序列被破坏

void ENC28J60_Init(void)
{
	ENC28J60_CS1; // 空闲状态下CS应该为高电平
	ENC28J60_SystemReset();

	// 设置接收缓冲区的起点和终点
	ENC28J60_Write(ERXSTL, 0); // 起点设为0可以避免网卡本身的bug使读指针跑飞
	ENC28J60_Write(ERXSTH, 0);
	ENC28J60_Write(ERXNDL, ENC_RECV_END & 0xff);
	ENC28J60_Write(ERXNDH, ENC_RECV_END >> 8);
	ENC28J60_Write(ERXRDPTL, 0); // 数据保护指针的位置
	ENC28J60_Write(ERXRDPTH, 0);

	// 配置MAC
	while ((ENC28J60_Read(ESTAT) & ESTAT_CLKRDY) == 0); // 等待MAC和PHY寄存器稳定
	ENC28J60_SelectBank(2);
	ENC28J60_Write(MACON1, MACON1_TXPAUS | MACON1_RXPAUS | MACON1_MARXEN); // 允许接收, 开流量控制
	ENC28J60_Write(MACON3, MACON3_PADCFG_0 | MACON3_TXCRCEN | MACON3_FRMLNEN | MACON3_FULDPX);
	ENC28J60_Write(MACON4, MACON4_DEFER);
	ENC28J60_Write(MABBIPG, 0x15);
	ENC28J60_Write(MAIPGL, 0x12);
	ENC28J60_Write(MAIPGH, 0x0c);

	// 设置网卡地址
	ENC28J60_SelectBank(3);
	ENC28J60_Write(MAADR1, UIP_ETHADDR0);
	ENC28J60_Write(MAADR2, UIP_ETHADDR1);
	ENC28J60_Write(MAADR3, UIP_ETHADDR2);
	ENC28J60_Write(MAADR4, UIP_ETHADDR3);
	ENC28J60_Write(MAADR5, UIP_ETHADDR4);
	ENC28J60_Write(MAADR6, UIP_ETHADDR5);

	// 配置PHY
	ENC28J60_WritePhy(PHCON1, PHCON1_PDPXMD); // 全双工模式

	// 允许接收数据包
	ENC28J60_Write(EIE, EIE_PKTIE | EIE_LINKIE | EIE_INTIE); // 如果收到了数据包, 或网络连接发生变化, 就触发中断
	ENC28J60_WritePhy(PHIE, PHIE_PLNKIE | PHIE_PGEIE); // 配置PHY中断 (监测网络连接变化)
	ENC28J60_Write(ECON1, ECON1_RXEN);
}

uint8_t ENC28J60_Read(uint8_t addr)
{
	uint8_t data;
	ENC28J60_CS0;
	SPI_Write(addr & 0x1f);
	data = SPI_Read(); // ETH寄存器
	if (addr & 0x80)
		data = SPI_Read(); // MAC和MII寄存器需要再读一次
	ENC28J60_CS1;
	return data;
}

void ENC28J60_ReadBuffer(uint8_t *data, uint16_t len)
{
	ENC28J60_CS0;
	SPI_Write(0x3a);
	while (len--)
		*data++ = SPI_Read();
	ENC28J60_CS1;
}

uint16_t ENC28J60_ReadPhy(uint8_t addr)
{
	uint16_t data;
	ENC28J60_SelectBank(2);
	ENC28J60_Write(MIREGADR, addr);
	ENC28J60_SetBits(MICMD, MICMD_MIIRD, ENCSET);
	ENC28J60_SelectBank(3);
	while (ENC28J60_Read(MISTAT) & MISTAT_BUSY);
	ENC28J60_SelectBank(2);
	ENC28J60_SetBits(MICMD, MICMD_MIIRD, ENCCLR);
	data = ENC28J60_Read(MIRDL);
	data |= ENC28J60_Read(MIRDH) << 8;
	return data;
}

void ENC28J60_SelectBank(uint8_t bank)
{
	uint8_t value = ENC28J60_Read(ECON1);
	bank &= ECON1_BSEL;
	if ((value & ECON1_BSEL) != bank)
	{
		value = (value & ~ECON1_BSEL) | bank;
		ENC28J60_Write(ECON1, value);
	}
}

// value: ENCSET/ENCCLR
void ENC28J60_SetBits(uint8_t addr, uint8_t mask, uint8_t value)
{
	ENC28J60_CS0;
	SPI_Write((addr & 0x1f) | value);
	SPI_Write(mask);
	ENC28J60_CS1;
}

void ENC28J60_SystemReset(void)
{
	ENC28J60_CS0;
	SPI_Write(0xff);
	ENC28J60_CS1;
}

void ENC28J60_Write(uint8_t addr, uint8_t value)
{
	ENC28J60_SetBits(addr, value, 0x40);
}

void ENC28J60_WriteBuffer(uint8_t *data, uint16_t len)
{
	ENC28J60_CS0;
	SPI_Write(0x7a);
	while (len--)
		SPI_Write(*data++);
	ENC28J60_CS1;
}

void ENC28J60_WritePhy(uint8_t addr, uint16_t value)
{
	ENC28J60_SelectBank(2);
	ENC28J60_Write(MIREGADR, addr);
	ENC28J60_Write(MIWRL, value & 0xff);
	ENC28J60_Write(MIWRH, value >> 8);
	ENC28J60_SelectBank(3);
	while (ENC28J60_Read(MISTAT) & MISTAT_BUSY);
}

uint8_t SPI_Write(uint8_t data)
{
	SPDR = data;
	while ((SPSR & _BV(SPIF)) == 0);
	return SPDR;
}

【主程序】
main.c:
// 晶振: 外部11.0592MHz
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sfr_defs.h>
#include "uip/uip_arp.h"
#include "uip/timer.h"
#include "uip/uip.h"
#include "ENC28J60.h"
//#include "USART.h" // 如果包含了这个头文件, 将向串口输出发送和接收的数据包, 以便调试

#define ETHHDR ((struct uip_eth_hdr *)&uip_buf[0])

const uint8_t seg8[] PROGMEM = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; // 共阳数码管段码表
uint8_t flag_disp = 0; // 是否插了网线, 初值必须为0 (开机时只有网线是插上的, 才会自动触发LINK中断)
uint8_t pkt_in = 0; // 是否有数据包进来
uint16_t num_disp = 0; // 数码管显示的数字
clock_time_t clocktime = 0; // 当前时钟值

clock_time_t clock_time(void)
{
	return clocktime;
}

void uip_appcall(void)
{
	/*
	下面的代码太浪费SRAM!
	const char *str = "HTTP/1.1 200 OK\r\nContent-Length: 19\r\n\r\n<b>Hello World!</b>";
	if (uip_newdata() || uip_rexmit())
		uip_send(str, strlen(str));
	*/

	const char *str = PSTR("HTTP/1.1 200 OK\r\nContent-Length: 52\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/html\r\n\r\n<b>Hello World!</b><br>This is a very long string!!!"); // 把字符串放到Flash里面
	extern void *uip_sappdata;
	extern u16_t uip_slen;
	if (uip_newdata() || uip_rexmit()) // 如果收到了客户端发来的数据, 或者需要重传HTML内容
	{
		// 相当于uip_send(str, strlen(str)), 但是是从Flash中读取内容
		for (uip_slen = 0; (((char *)uip_sappdata)[uip_slen] = pgm_read_byte(&str[uip_slen])) != '\0'; uip_slen++);
	}
}

void uip_udp_appcall(void)
{
	
}

void myapp_init(void)
{
	uip_listen(HTONS(80));
}

void read_packet(void)
{
	uint16_t status[2];
	static uint16_t next_ptr = 0;

#ifdef USART_H_
	USART_SendString(PSTR("Recv: EPKTCNT="));
	ENC28J60_SelectBank(1);
	USART_SendNumber(ENC28J60_Read(EPKTCNT));
	USART_SendString(PSTR(", start="));
	USART_SendNumber(next_ptr);
#endif

	ENC28J60_SelectBank(0);
	num_disp = ENC28J60_Read(ERXWRPTL) | (ENC28J60_Read(ERXWRPTH) << 8); // 数码管上显示接收写指针的位置
	ENC28J60_Write(ERDPTL, next_ptr & 0xff); // 将读指针移动到当前数据包处
	ENC28J60_Write(ERDPTH, next_ptr >> 8);

	ENC28J60_ReadBuffer((uint8_t *)&next_ptr, sizeof(next_ptr)); // 读取下一个数据包的位置
	ENC28J60_ReadBuffer((uint8_t *)status, sizeof(status)); // status vector
	uip_len = status[0] - 4; // 数据包大小

#ifdef USART_H_
	USART_SendString(PSTR(", len="));
	USART_SendNumber(uip_len);
	USART_SendString(PSTR(", next="));
	USART_SendNumber(next_ptr);
	USART_SendString(PSTR(", status="));
	USART_SendNumber(status[1]);
	USART_SendString(PSTR("\r\n"));
#endif
	
	if (uip_len <= UIP_BUFSIZE)
	{
		ENC28J60_ReadBuffer(uip_buf, uip_len);
#ifdef USART_H_
		output_packet();
#endif
	}
	else
		uip_len = 0; // 内存不足, 丢弃
	// 注意: 数据包与数据包之间可能有填充字节

	ENC28J60_Write(ERXRDPTL, next_ptr & 0xff); // 允许之后接收的数据将该区域覆盖
	ENC28J60_Write(ERXRDPTH, next_ptr >> 8);
	ENC28J60_SetBits(ECON2, ECON2_PKTDEC, ENCSET); // 数据包数减1
}

void send_packet(void)
{
	GICR &= ~_BV(INT2);
	ENC28J60_SelectBank(0);
	ENC28J60_Write(ETXSTL, ENC_SEND_START & 0xff); // 数据首地址
	ENC28J60_Write(ETXSTH, ENC_SEND_START >> 8);
	ENC28J60_Write(ETXNDL, (ENC_SEND_START + uip_len) & 0xff); // 数据尾地址
	ENC28J60_Write(ETXNDH, (ENC_SEND_START + uip_len) >> 8);

	ENC28J60_Write(EWRPTL, ENC_SEND_START & 0xff); // 设置写指针位置
	ENC28J60_Write(EWRPTH, ENC_SEND_START >> 8);
	ENC28J60_WriteBufferByte(0); // 写入控制字节
	ENC28J60_WriteBuffer(uip_buf, uip_len); // 写入要发送的数据
	ENC28J60_SetBits(ECON1, ECON1_TXRTS, ENCSET); // 开始发送

#ifdef USART_H_
	USART_SendString(PSTR("Send: len="));
	USART_SendNumber(uip_len);
	USART_SendString(PSTR("\r\n"));
	output_packet();
#endif

	while (ENC28J60_Read(ECON1) & ECON1_TXRTS); // 等待发送完毕
	GICR |= _BV(INT2);
}

int main(void)
{
	struct timer arp_timer, periodic_timer;
	uint8_t i;
	uip_ipaddr_t ipaddr;

	// 看门狗配置
	WDTCR = _BV(WDE) | _BV(WDP2) |  _BV(WDP1) | _BV(WDP0);

#ifdef USART_H_
	// 串口配置
	UBRRL = 5; // 波特率: 115200
	UCSRB = _BV(TXEN); // 允许发送
	// 由于SRAM空间不足, printf功能无法使用
#endif

	// SPI端口配置
	DDRB = _BV(DDB7) | _BV(DDB5) | _BV(DDB4);
	SPSR = _BV(SPI2X); // 选择2分频: 11.0592MHz/2=5.5296MHz, 远低于最高允许速度20MHz
	SPCR = _BV(SPE) | _BV(MSTR); // 开SPI, 设为主模式

	// 中断引脚配置(INT2_PB2): 下降沿触发
	MCUCSR &= ~_BV(ISC2);
	// 注意: 即使GICR中的INT2没有打开, 但只要INT2上有下降沿, GIFR中的INTF2标志也会置位
	// 只有此后打开了INT2中断和全局中断, 才执行中断函数

	// 数码管动态扫描配置
	DDRA = 0xff; // 配置段选端口
	PORTA = 0xff; // 熄灭数码管
	DDRC = _BV(DDB7) | _BV(DDB6) | _BV(DDB5) | _BV(DDB4) | _BV(DDB3) | _BV(DDB0); // 配置位选端口
	sei(); // 开总中断
	TIMSK |= _BV(TOIE0); // 开定时器中断
	TCNT0 = 0xff; // 先让定时器溢出一次, 点亮数码管
	TCCR0 |= _BV(CS02); // 开定时器0: 设为256分频, 总溢出时间约为5.926ms

	// uip时钟定时器
	TIMSK |= _BV(TOIE2);
	TCNT2 = 40; // 定时20ms
	TCCR2 |= _BV(CS22) | _BV(CS20); // 1024分频

	timer_set(&arp_timer, CLOCK_SECOND * 10);
	timer_set(&periodic_timer, CLOCK_SECOND / 2);

	ENC28J60_Init();
	uip_init();
	uip_ipaddr(ipaddr, 192, 168, 1, 50); // IP地址
	uip_sethostaddr(ipaddr);
	uip_ipaddr(ipaddr, 192, 168, 1, 1); // 网关
	uip_setdraddr(ipaddr);
	uip_ipaddr(ipaddr, 255, 255, 255, 0); // 子网掩码
	uip_setnetmask(ipaddr);

	myapp_init();

	GICR |= _BV(INT2); // 开网卡中断
	while (1)
	{
		// 读取一个数据包
		if (pkt_in)
		{
			asm("wdr");
			GICR &= ~_BV(INT2); // 进入临界区之前必须关网卡中断!
			read_packet();
			ENC28J60_SelectBank(1);
			if (!ENC28J60_Read(EPKTCNT)) // 若已接收完全部数据包
			{
				pkt_in = 0;
				ENC28J60_SetBits(EIE, EIE_PKTIE, ENCSET); // 则重开数据包接收中断
			}
			GICR |= _BV(INT2); // 重开网卡中断
		}

		if (uip_len > 0)
		{
			asm("wdr");
			if (ETHHDR->type == htons(UIP_ETHTYPE_IP))
			{
				uip_arp_ipin();
				uip_input();
				if (uip_len > 0)
				{
					uip_arp_out();
					send_packet();
				}
			}
			else if (ETHHDR->type == htons(UIP_ETHTYPE_ARP))
			{
				uip_arp_arpin();
				if (uip_len > 0)
					send_packet();
			}
		}
		else if (timer_expired(&periodic_timer))
		{
			asm("wdr"); // 喂狗
			timer_reset(&periodic_timer);
			for (i = 0; i < UIP_CONNS; i++)
			{
				uip_periodic(i);
				if (uip_len > 0)
				{
					uip_arp_out();
					send_packet();
				}
			}
#if UIP_UDP
			for (i = 0; i < UIP_UDP_CONNS; i++)
			{
				uip_udp_periodic(i);
				if (uip_len > 0)
				{
					uip_arp_out();
					send_packet();
				}
			}
#endif
		}

		if (timer_expired(&arp_timer))
		{
			timer_reset(&arp_timer);
			uip_arp_timer();
		}
	}
}

// 网卡中断
ISR(INT2_vect)
{
	uint8_t status;
	// 现在INT2为低电平
	ENC28J60_SetBits(EIE, EIE_INTIE, ENCCLR); // 该语句执行完毕后, INT2引脚会回到高电平, 之后新来的网卡中断都将处于pending状态
	// 如果在执行该函数期间恰好又来了一个中断, 那么肯定能被本次中断函数处理到
	sei(); // 允许数码管扫描中断抢占本中断, 防止数码管闪烁
	
	status = ENC28J60_Read(EIR); // 获取所有网卡中断的状态
	// 一个一个处理:
	if (status & EIR_PKTIF)
	{
		/* 收到新数据包 */
		pkt_in = 1;
		ENC28J60_SetBits(EIE, EIE_PKTIE, ENCCLR); // 暂时关闭该中断
	}
	if (status & EIR_LINKIF)
	{
		ENC28J60_ReadPhy(PHIR); // 清除中断标志
		flag_disp = ENC28J60_IsPluggedIn();
	}

	// 处理其他中断: if (status & ....) {....} // 不能加else!

	GICR &= ~_BV(INT2);
	ENC28J60_SetBits(EIE, EIE_INTIE, ENCSET); // 如果还有新来的中断没处理, 那么INT2将出现下降沿, 退出后再次执行本函数
	cli();
	GICR |= _BV(INT2);
	// 退出时将自动执行sei();
}

// 数码管动态扫描
// 每次只扫描一位, 从低位到高位
ISR(TIMER0_OVF_vect)
{
	static uint16_t numbuf;
	static uint8_t mask = _BV(PORTC7);
	TCNT0 = 0x90; // 每个数码管点亮的时间: (256-144)/256 * 5.926ms = 2.592625ms
	if (mask == _BV(PORTC7))
		numbuf = num_disp; // 重装数字

	PORTC |= _BV(PORTC7) | _BV(PORTC6) | _BV(PORTC5) | _BV(PORTC4) | _BV(PORTC3) | _BV(PORTC0); // 熄灭之前点亮的数码管
	PORTA = pgm_read_byte(&seg8[numbuf % 10]); // 设置显示字符
	PORTC &= ~mask; // 点亮数码管
	
	// 下一次要点亮的数码管
	mask >>= 1;
	if (mask == _BV(PORTC2))
	{
		mask = _BV(PORTC0);
		numbuf = flag_disp;
	}
	else if (mask == 0) // 若已扫描完一遍
		mask = _BV(PORTC7); // 则回到最低位
	else
		numbuf /= 10;
}

// uip定时中断
ISR(TIMER2_OVF_vect)
{
	TCNT2 = 40;
	clocktime++;
}

【调试用的串口输出】
USART.h:
#ifndef USART_H_
#define USART_H_

void USART_SendChar(uint8_t ch);
void USART_SendNumber(uint16_t num);
void USART_SendString(const char *str); // str必须加PSTR()
void output_packet(void);

#endif /* USART_H_ */
USART.c:
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "uip/uip.h"

void USART_SendChar(uint8_t ch)
{
	UDR = ch;
	while ((UCSRA & _BV(UDRE)) == 0);
}

void USART_SendNumber(uint16_t num)
{
	uint8_t i, ch;
	for (i = 0; (i & 7) < 5; i++)
	{
		if (i == 0)
		{
			ch = '0' + num / 10000;
			num %= 10000;
		}
		else
		{
			ch = '0' + num / 1000;
			num = num % 1000 * 10;
		}
		if (ch != '0' || (i & 0x84) != 0)
		{
			i |= 0x80;
			USART_SendChar(ch);
		}
	}
}

void USART_SendString(const char *str)
{
	char c;
	while ((c = pgm_read_byte(str++)) != '\0')
		USART_SendChar(c);
}

void output_packet(void)
{
	uint16_t i;
	for (i = 0; i < uip_len; i++)
	{
		if ((uip_buf[i] >> 4) > 9)
			USART_SendChar('A' + (uip_buf[i] >> 4) - 10);
		else
			USART_SendChar('0' + (uip_buf[i] >> 4));
		if ((uip_buf[i] & 0x0f) > 9)
			USART_SendChar('A' + (uip_buf[i] & 0x0f) - 10);
		else
			USART_SendChar('0' + (uip_buf[i] & 0x0f));
	}
	USART_SendString(PSTR("\r\n"));
}

将这五个文件添加到项目中后编译,然后用AVR Fighter和ISP下载线下载到开发板上,并把网线插到路由器上,一段时间后,数码管上显示的数字如下:
1    00686
左边的1表示插了网线,右边的686是当前接收缓冲区写指针的位置。

在电脑或手机上用浏览器访问: http://192.168.1.50/,可以看到网页内容:
Hello World!
This is a very long string!!!

注意MAC地址的第一个字节必须为偶数。因为如果为奇数,有些路由器就会将该MAC地址识别为多播地址,导致发出去的数据包丢失。

【照片/截图】



uip协议栈中本身没有Flash版本的uip_send函数,我们可以在uip.c的底部仿照uip_send函数添加一个uip_send_P函数:
// 添加的函数
void
uip_send_P(const void *data)
{
	int len = strlen_P(data);
	if(len > 0) {
		uip_slen = len;
		if(data != uip_sappdata) {
			memcpy_P(uip_sappdata, (data), uip_slen);
		}
	}
}
该函数取消了原有的len参数,自动计算字符串的长度。
然后,在uip.c的头部包含以下头文件:
#include <avr/pgmspace.h>
在uip.h文件最后添加该函数的声明:
// .........

void uip_send_P(const void *data);
#endif

最后修改uip_appcall函数:
void uip_appcall(void)
{
	if (uip_newdata() || uip_rexmit())
		uip_send_P(PSTR("HTTP/1.1 200 OK\r\nContent-Length: 59\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nContent-Type: text/html\r\n\r\n<b>Hello World!</b><br>This is a very <i>long</i> string!!!"));
}

编译结果:
(没开串口输出)
Program Memory Usage : 8300 bytes   50.7 % Full
Data Memory Usage       : 874 bytes   85.4 % Full
(开了串口输出)
Program Memory Usage : 8942 bytes   54.6 % Full
Data Memory Usage    : 874 bytes   85.4 % Full

Flash占用空间有所增加是因为我们新定义了uip_send_P函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值