|
【程序】使用数码管显示lwip网页访问的次数 |
一派護法 十九級 |
修改前的工程: https://zh.arslanbar.net/post.php?t=24586【最终效果】 打开网页时,自动把数字加1,并显示数码管上显示的数字。 例如访问网页“http://enc28j60/wel”时,显示的内容如下: 请求的网页文件名称是: /wel num=170 同时,数码管动态扫描显示数字170。
|
一派護法 十九級 |
首先打开main.c文件,添加全局变量: uint8_t num = 0; const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; 在main函数中的lwip_init函数前,添加如下代码: // 配置数码管扫描 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 数码管扫描端口 (74HC595驱动) GPIOB->CRH = 0x00000033; GPIOB->CRL = 0x30000000; RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; // 用定时器7来定时扫描 TIM7->ARR = 49; // 扫描频率: 50*0.1ms=5ms TIM7->PSC = 7199; // 0.1ms基准 TIM7->DIER = TIM_DIER_UIE; NVIC_EnableIRQ(TIM7_IRQn); // 开中断 TIM7->EGR = TIM_EGR_UG; // 触发中断, 立即点亮数码管 TIM7->CR1 = TIM_CR1_CEN; // 开定时器
|
一派護法 十九級 |
在main函数下添加定时器7的中断服务函数: void TIM7_IRQHandler(void) { static uint8_t i = 0; static uint8_t numbuf; TIM7->SR &= ~TIM_SR_UIF; if (i == 0) numbuf = num; ser_in(seg8[numbuf % 10]); ser_in(1 << i); par_out(); numbuf /= 10; i = (i + 1) % 3; // 总共3个数码管 }
该函数每次只点亮一个数码管,从低位扫描到高位。
|
一派護法 十九級 |
本程序的关键是解决数码管扫描中断打断lwip和网卡驱动原本的程序而卡死的问题。有两种解决方法: 1. 在main函数的主循环中开关中断: while (1) { __disable_irq(); // lwip处理 __enable_irq(); } 2. 把上述语句添加在网卡驱动程序中与SPI有关的函数里。主要是ENC28J60.c文件中如下4个函数: ENC28J60_ReadBuf ENC28J60_ReadOP ENC28J60_WriteBuf ENC28J60_WriteOP 显然方法2要更好一些,毕竟关中断的时间要比方法1短得多。数码管的扫描也就不容易出现闪烁。
|
一派護法 十九級 |
采用方法2后,就算在浏览器里一直按着F5刷新键不放,程序也不会卡死,并且数码管丝毫没有闪烁。
|
一派護法 十九級 |
【修改后的4个网卡驱动函数】 // 读缓冲区 void ENC28J60_ReadBuf(uint8_t *p, uint16_t len) { uint8_t data; uint8_t first = 1; if (len == 0) return; __disable_irq(); SPI1->CR1 |= SPI_CR1_SPE; SPI1->DR = ENC28J60_READ_BUF_MEM; // 通过SPI发送读取缓冲区命令 while ((SPI1->SR & SPI_SR_TXE) == 0); // 循环读取 while (len--) { SPI1->DR = 0xff; // 送入下一次要发送的数据 while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待本次数据发送完毕 data = SPI1->DR; // 接收本次数据 if (first) first = 0; // 忽略第一次数据 else *p++ = data; while ((SPI1->SR & SPI_SR_TXE) == 0); } while ((SPI1->SR & SPI_SR_RXNE) == 0); *p = SPI1->DR; // 接收最后一次数据 while (SPI1->SR & SPI_SR_BSY); SPI1->CR1 &= ~SPI_CR1_SPE; __enable_irq(); }
uint8_t ENC28J60_ReadOP(uint8_t op, uint8_t addr) { uint8_t data; __disable_irq(); SPI1->CR1 |= SPI_CR1_SPE; // 启动SPI总线, 同时自动拉低片选信号CS SPI1->DR = op | (addr & 0x1f); // 操作码和地址 (1) while ((SPI1->SR & SPI_SR_TXE) == 0); // TXE置位时数据(1)刚好发完1位, 可以向DR中放入下次要发送的数据 SPI1->DR = 0xff; // 送入数据 (2) while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(1)发送完毕 data = SPI1->DR; // 接收 (1) while ((SPI1->SR & SPI_SR_TXE) == 0); // TXE置位时数据(2)刚好发完1位 if (addr & 0x80) // 如果是MAC和MII寄存器, 第一个读取的字节无效, 该信息包含在地址的最高位 SPI1->DR = 0xff; // 送入数据 (3), 再次通过SPI读取数据 while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(2)发送完毕 data = SPI1->DR; // 接收 (2) if (addr & 0x80) { while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(3)发送完毕 data = SPI1->DR; // 接收 (3) } while (SPI1->SR & SPI_SR_BSY); SPI1->CR1 &= ~SPI_CR1_SPE; // 关闭SPI总线, 同时自动拉高片选信号CS __enable_irq(); return data; }
// 写缓冲区 void ENC28J60_WriteBuf(uint8_t *p, uint16_t len) { __disable_irq(); SPI1->CR1 |= SPI_CR1_SPE; SPI1->DR = ENC28J60_WRITE_BUF_MEM; // 发送写取缓冲区命令 while ((SPI1->SR & SPI_SR_TXE) == 0); // 循环发送 while (len--) { SPI1->DR = *p++; while ((SPI1->SR & SPI_SR_TXE) == 0); } while (SPI1->SR & SPI_SR_BSY); SPI1->CR1 &= ~SPI_CR1_SPE; __enable_irq(); }
void ENC28J60_WriteOP(uint8_t op, uint8_t addr, uint8_t data) { __disable_irq(); SPI1->CR1 |= SPI_CR1_SPE; SPI1->DR = op | (addr & 0x1f); // 发送操作码和寄存器地址 while ((SPI1->SR & SPI_SR_TXE) == 0); SPI1->DR = data; // 发送数据 while ((SPI1->SR & SPI_SR_TXE) == 0); while (SPI1->SR & SPI_SR_BSY); data = SPI1->DR; // 清RxNE SPI1->CR1 &= ~SPI_CR1_SPE; __enable_irq(); }
|
一派護法 十九級 |
现在需要把数码管上的数字显示到网页上。打开httptest.c文件,按提示修改代码: #include <string.h> #include "lwip/tcp.h" // 一般情况下需要包含的头文件
#define STR_AND_SIZE(str) (str), strlen(str)
extern uint8_t num; // ★导入main.c文件中的全局变量
err_t http_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { // ............ if (p != NULL) { // 提取页面名称 // ................. // 生成HTML内容 num++; // ★网页上显示的数字应该和数码管上的一样 sprintf(str, "<meta charset=\"gb2312\"><title>获取网页名称</title><div style=\"font-family:Arial\"><b>请求的网页文件名称是: </b>%s<br><b>num=</b>%d</div>", name, num); // ★修改网页内容
..............................
|
一派護法 十九級 |
不过,如果收发大量的数据包,ENC28J60_WriteBuf或ENC28J60_ReadBuf的执行时间过长,那么中断就会关闭很长的时间,这样数码管就有可能闪烁。为了改善这种情况,可以考虑把SPI的操作都改成中断的形式,并且该中断的抢占优先级要比数码管的高,并注意不影响74HC595的时序,这样效果就要好得多。
|
一派護法 十九級 |
最后为了保险起见,最好在主程序中使用独立看门狗IWDG,并及时喂狗。万一程序由于某种原因死机了,可以自动恢复过来而不至于服务器宕机导致网页打不开。 可以把自动分配的IP地址和数码管上显示的数字放到BKP备用寄存器里面,复位之后不会丢失。
|
一派護法 十九級 |
【不关中断时程序卡死在SPI的while循环里的原因】 程序流程如下(连续发送模式): 送数据1; 等DR变为空(TXE=0); 送数据2; 等数据1发送完毕(RXNE=0); (1) 接收数据1; (2) 等数据2发送完毕(RXNE=0); (3) 接收数据2;
如果数据2已经发送完毕了数据1都还没读取,那么数据1将会丢失。 由于没有关闭中断,那么在等待数据1发送完毕(步骤1)时进了数码管扫描中断,那么等到中断执行完毕后,数据2早就发送完毕了,因此步骤2实际接收到的是数据2而非数据1,数据1已经丢失。并且由于一直没有新的数据到来,RXNE始终不会被置1,因此程序就卡死在了(3)里面。
|
一派護法 十九級 |
解决办法除了关闭中断以外,还可以把程序流程改成非连续发送模式: 送数据1; 等待数据1发送完毕; // while循环 接收数据1; 送数据2; 等待数据2发送完毕; // while循环 接收数据2; 这样就可以保证一定不会出问题。
如果一定要使用连续发送模式,那么就应该使用SPI接收中断,其抢占优先级应该比数码管扫描中断更高,这样就能确保不会丢失数据,程序也不会卡死在while循环里。
|