完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
【芯片环境】 单片机:ATMega16A 晶振:外部11.0592MHz 蜂鸣器接在PD7上,网卡中断为INT2 数码管段选PA,位选从高位到低位为PC0到PC7 【main.c】 #include This is a very long string!!! Connection ID: ")); 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 【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=%drn"), ENC28J60_Read(EIE), ENC28J60_Read(EPKTCNT), ENC28J60_Read(ECON1), ENC28J60_Read(ERXFCON), ENC28J60_Read(EIR)); for (i = 0; buf != ' |