1. 程式人生 > >【抗干擾程式碼】AVR微控制器自動復位由於電壓不足導致ENC28J60網絡卡出現的故障

【抗干擾程式碼】AVR微控制器自動復位由於電壓不足導致ENC28J60網絡卡出現的故障

【晶片環境】

微控制器:ATMega16A

晶振:外部11.0592MHz

蜂鳴器接在PD7上,網絡卡中斷為INT2

數碼管段選PA,位選從高位到低位為PC0到PC7

【main.c】

#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sfr_defs.h>
#include <stdio.h>
#include "uip/uip_arp.h"
#include "uip/timer.h"
#include "uip/uip.h"
#include "ENC28J60.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; // 當前時鐘值
uint16_t next_ptr = 0;

clock_time_t clock_time(void)
{
	return clocktime;
}

void uip_appcall(void)
{
	char ch;
	if (uip_connected())
		uip_conn->appstate = 0;
	if (uip_newdata())
		uip_conn->appstate = 1;
	if (uip_acked())
		uip_conn->appstate++;

	if (uip_acked() || uip_newdata() || uip_rexmit())
	{
		switch (uip_conn->appstate)
		{
		case 1:
			uip_send_P(PSTR("HTTP/1.1 200 OK\r\nContent-Length: 86\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!!!<br><b>Connection ID: </b>"));
			break;
		case 2:
			ch = '0' + (uip_conn - uip_conns);
			uip_send(&ch, 1);
			break;
		case 3:
			uip_close();
		}
	}
}

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

void read_packet(void)
{
	uint16_t status[2];

	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; // 資料包大小
	
	if (uip_len <= UIP_BUFSIZE)
		ENC28J60_ReadBuffer(uip_buf, uip_len);
	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); // 開始傳送

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

void beep(void)
{
	uint8_t old = TCCR2;
	uint16_t i;
	TIMSK &= ~_BV(TOIE2); // 關閉定時器2中斷
	TCCR2 = _BV(WGM20) | _BV(COM21) | _BV(CS21); // Phase Correct PWM, 8分頻: 2.71kHz
	TIFR = _BV(TOV2);
	for (i = 0; i < 2000; i++) // 738ms
	{
		while ((TIFR & _BV(TOV2)) == 0);
		TIFR = _BV(TOV2); // 清除標誌
	}
	TCCR2 = old;
	TIMSK |= _BV(TOIE2); // 重開定時器2中斷
}

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

	ACSR = _BV(ACD); // 禁用模擬比較器

	// 蜂鳴器配置
	PORTD = _BV(PORTD7);
	DDRD = _BV(DDB7);
	OCR2 = 0x80; // 佔空比50%
	if (MCUCSR & _BV(WDRF))
	{
		// 若觸發了看門狗復位, 則蜂鳴器響鈴
		MCUCSR &= ~_BV(WDRF);
		beep();
	}

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

	// 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(DDC7) | _BV(DDC6) | _BV(DDC5) | _BV(DDC4) | _BV(DDC0); // 配置位選埠
	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 (timer_expired(&arp_timer))
		{
			asm("wdr");
			timer_reset(&arp_timer);
			uip_arp_timer();

			/* ----- 抗干擾 ----- */
			// 若網絡卡控制器自身發生了復位, 或者中斷被意外關閉, 則需要重新初始化 (一般都是因為供電不足導致的)
			if (!pkt_in)
			{
				GICR &= ~_BV(INT2); // 進入臨界區, 關網絡卡中斷, 防止時序錯亂
				if ((ENC28J60_Read(ECON1) & ECON1_RXEN) == 0) // 檢測到RXEN意外接0, 接收模組停止工作
				{
					// 蜂鳴器發出警報
					for (i = 0; i < 4; i++)
					{
						asm("wdr");
						beep();
					}

					// 先暫時讓微控制器進入低功耗模式
					// 增加電壓不足的情況下網絡卡恢復正常工作的可能性
					PORTA = 0xff; // 熄滅數碼管
					TCNT2 = 0; // 用定時器2中斷作為喚醒源
					// 在這裡應將其他耗電量大的裝置全部關閉
					MCUCR |= _BV(SE) | _BV(SM0); // ADC Noise Reduction Mode
					asm("sleep");
					MCUCR &= ~_BV(SE);

					// 網絡卡復位
					next_ptr = 0;
					ENC28J60_Init();
					timer_reset(&arp_timer);

					// 如果網絡卡電壓不夠, 那麼這裡復位後雖然配置是正確的, 但無法接收任何資料包
					// 再插一根USB線供電即可解決此問題
				}
				GICR |= _BV(INT2);
			}
		}
	}
}

// 網絡卡中斷
ISR(INT2_vect)
{
	uint8_t status;
	// 現在INT2為低電平
	ENC28J60_SetBits(EIE, EIE_INTIE, ENCCLR); // 該語句執行完畢後, INT2引腳會回到高電平, 之後新來的網絡卡中斷都將處於pending狀態
	// 如果在執行該函式期間恰好又來了一箇中斷, 那麼肯定能被本次中斷函式處理到
	GICR &= ~_BV(INT2); // 開全域性中斷前, 應防止INT2引腳由於外部干擾導致重入本中斷函式
						// 如果幹擾時間過長, 看門狗將自動復位
	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!

	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(PORTC0); // 熄滅之前點亮的數碼管
	PORTA = pgm_read_byte(&seg8[numbuf % 10]); // 設定顯示字元
	PORTC &= ~mask; // 點亮數碼管
	
	// 下一次要點亮的數碼管
	mask >>= 1;
	if (mask == _BV(PORTC3))
	{
		mask = _BV(PORTC0);
		numbuf = flag_disp;
	}
	else if (mask == 0) // 若已掃描完一遍
		mask = _BV(PORTC7); // 則回到最低位
	else
		numbuf /= 10;
}

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

【ENC28J60.c】

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

// 注意: 執行這些函式時一定要先關閉網絡卡中斷!!! 防止SPI序列被破壞
extern uint8_t flag_disp;

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); // 全雙工模式
	flag_disp = ENC28J60_IsPluggedIn(); // 獲取初始網路連線狀態

	// 允許接收資料包
	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;
}


【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_ */


【除錯用程式碼:串列埠輸出暫存器狀態】

void status_check(void)
{
    char buf[80];
    uint8_t i;
    UBRRL = 5;
    UCSRB = _BV(TXEN);
    ENC28J60_SelectBank(1);
    sprintf_P(buf, PSTR("EIE=%d, EPKTCNT=%d, ECON1=%d, ERXFCON=%d, EIR=%d\r\n"), ENC28J60_Read(EIE), ENC28J60_Read(EPKTCNT), ENC28J60_Read(ECON1), ENC28J60_Read(ERXFCON), ENC28J60_Read(EIR));
    for (i = 0; buf[i] != '\0'; i++)
    {
        UDR = buf[i];
        while ((UCSRA & _BV(UDRE)) == 0);
    }
}

用一個功率較小的手機充電器上的USB介面作為微控制器的電源來測試。測試過程中由於電流不足、電壓不穩定,蜂鳴器響了十幾次(出現Request timed out.的地方),但最終網絡卡都成功復位了。
【可靠性測試結果】
C:\Users\Octopus>ping 192.168.1.50 -n 100

Pinging 192.168.1.50 with 32 bytes of data:
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=15ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=10ms TTL=128
Reply from 192.168.1.50: bytes=32 time=33ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=28ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=15ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=9ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=42ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=15ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=9ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=27ms TTL=128
Reply from 192.168.1.50: bytes=32 time=35ms TTL=128
Reply from 192.168.1.50: bytes=32 time=14ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128

Ping statistics for 192.168.1.50:
    Packets: Sent = 100, Received = 82, Lost = 18 (18% loss),
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 42ms, Average = 5ms

C:\Users\Octopus>

在下面的測試中使用單根USB線供電,電壓環境極不穩定。最後微控制器進入睡眠模式也未能使網絡卡恢復正常工作,數碼管最終顯示1    0524,ping的結果為“Reply from 192.168.1.2: Destination host unreachable.”。
不過幾分鐘過去後,蜂鳴器響了起來,網絡卡再一次恢復工作。
【測試結果】
C:\Users\Octopus>ping 192.168.1.50 -n 100

Pinging 192.168.1.50 with 32 bytes of data:
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=10ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=22ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=15ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=12ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=21ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=10ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=11ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.2: Destination host unreachable.
Reply from 192.168.1.50: bytes=32 time=2005ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=1002ms TTL=128
Request timed out.
Request timed out.
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Reply from 192.168.1.2: Destination host unreachable.
Reply from 192.168.1.2: Destination host unreachable.
Reply from 192.168.1.2: Destination host unreachable.
Reply from 192.168.1.2: Destination host unreachable.

Ping statistics for 192.168.1.50:
    Packets: Sent = 100, Received = 47, Lost = 53 (53% loss),
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 2005ms, Average = 76ms

C:\Users\Octopus>

C:\Users\Octopus>ping 192.168.1.50 -n 100

Pinging 192.168.1.50 with 32 bytes of data:
Reply from 192.168.1.2: Destination host unreachable.
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Request timed out.
Request timed out.

Ping statistics for 192.168.1.50:
    Packets: Sent = 9, Received = 5, Lost = 4 (44% loss),
Approximate round trip times in milli-seconds:
    Minimum = 2ms, Maximum = 4ms, Average = 2ms
Control-C
^C
C:\Users\Octopus>

現在改用兩根USB線來供電,網絡卡終於穩定工作。
不過為了保險起見,程式中仍應該保留網絡卡自動復位的程式碼。
【測試結果】
C:\Users\Octopus>ping 192.168.1.50 -n 100

Pinging 192.168.1.50 with 32 bytes of data:
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=11ms TTL=128
Reply from 192.168.1.50: bytes=32 time=37ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=46ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=10ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=13ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=20ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=13ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=27ms TTL=128
Reply from 192.168.1.50: bytes=32 time=29ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=9ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=44ms TTL=128
Reply from 192.168.1.50: bytes=32 time=5ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=9ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=13ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=17ms TTL=128
Reply from 192.168.1.50: bytes=32 time=70ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=9ms TTL=128
Reply from 192.168.1.50: bytes=32 time=4ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=7ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=71ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=32ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=6ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=16ms TTL=128
Reply from 192.168.1.50: bytes=32 time=22ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128
Reply from 192.168.1.50: bytes=32 time=3ms TTL=128
Reply from 192.168.1.50: bytes=32 time=2ms TTL=128
Reply from 192.168.1.50: bytes=32 time=1ms TTL=128

Ping statistics for 192.168.1.50:
    Packets: Sent = 100, Received = 100, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 71ms, Average = 7ms

C:\Users\Octopus>